[转] Android CustomView

2018-08-22  本文已影响0人  兔斯基第2号

作者:吴小龙
http://wuxiaolong.me/2016/01/01/CustomView/

View工作流程

View工作流程主要指measure、layout、draw这三个流程,即测量、布局和绘制,其中measure确定View的自身的宽高,layout确定View在父容器放置的位置,draw将View绘制到屏幕上。

measure

为了更好理解measure过程,先了解MeasureSpec,MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize(这句话不知道几个意思)。SpecMode指的测量模式,SpecSize指在某种测量模式下规格大小。

SpecMode有三种:

自定义控件不重写onMeasure(),View类默认只支持EXACTLY模式,如果让View支持wrap_content属性,必须重写onMeasure()来指定wrap_content大小。代码如下:

private int mWidth = 500, mHeight = 450;

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);

    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    int finalWidth = mWidth > widthSpecSize ? mWidth : widthSpecSize;

    int finalheight = mHeight > heightSpecSize ? mHeight : heightSpecSize;

    //view支持wrap_content

    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {

        setMeasuredDimension(finalWidth, finalheight);

    } else if (widthSpecMode == MeasureSpec.AT_MOST) {

        setMeasuredDimension(finalWidth, heightSpecSize);

    } else if (heightSpecMode == MeasureSpec.AT_MOST) {

        setMeasuredDimension(widthSpecSize, finalheight);

    }

}

setMeasuredDimension方法会设置View宽/高的测量值

layout

layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,他在onLayout会遍历所有子元素并调用其layout方法。
layout方法大致流程:首先通过setFrame设定View四个顶点位置,View四个顶点一旦确定,那么它在父容器中位置也就确定了。

draw

重写onDraw()方法,并在Canvas对象上绘制所需要的图形,简而言之,有画笔和画布就可:
Android 画笔Paint
Android 画布Canvas
Android之画笔Paint和画布Canvas及实例练习圆角、刮刮卡、圆形头像、倒影效果

自定义attrs属性

attrs.xml

values目录下创建attrs.xml

<resources>

    <!--<declare-styleable>声明自定义属性-->

        <declare-styleable name="EmptyView">

        <!--<attr>声明具体自定义属性-->

        <attr name="empty_text" format="string"/>

        <attr name="empty_text_color" format="color"/>

        <attr name="empty_image" format="reference"/>

    </declare-styleable>

</resources>

这里format的color指的颜色。
其他:reference指资源ID;
dimension指尺寸;
string、integer、boolean指基本数据类型。
也可以用“|”来分隔不同的属性。

完整代码

public class EmptyView extends View {

    private int mWidth = 500, mHeight = 450;

    private Paint mPaint;

    private String emptyText;

    private Drawable emptyImage;

    private Bitmap bitmap;

    private Rect textRect;

    private Context context;

    public EmptyView(Context context) {

        super(context);

        this.context = context;

        initView();

    }

    public EmptyView(Context context, AttributeSet attrs) {

        this(context, attrs, 0);

    }

    public EmptyView(Context context, AttributeSet attrs, int defStyleAttr) {

        super(context, attrs, defStyleAttr);

        this.context = context;

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.EmptyView);

        emptyImage = typedArray.getDrawable(R.styleable.EmptyView_empty_image);

        emptyText = typedArray.getString(R.styleable.EmptyView_empty_text);

        typedArray.recycle();

        initView();

    }

    private void initView() {

        BitmapDrawable bitmapDrawable = (BitmapDrawable) emptyImage;

        bitmap = bitmapDrawable.getBitmap();

        mPaint = new Paint();

        mPaint.setColor(Color.GRAY);

        mPaint.setTextSize(40f);

        mPaint.setAntiAlias(true);

        textRect = new Rect();

        mPaint.getTextBounds(emptyText, 0, emptyText.length(), textRect);

    }

    @Override

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);

        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        int finalWidth = mWidth > widthSpecSize ? mWidth : widthSpecSize;

        int finalheight = mHeight > heightSpecSize ? mHeight : heightSpecSize;

        //view支持wrap_content

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {

            setMeasuredDimension(finalWidth, finalheight);

        } else if (widthSpecMode == MeasureSpec.AT_MOST) {

            setMeasuredDimension(finalWidth, heightSpecSize);

        } else if (heightSpecMode == MeasureSpec.AT_MOST) {

            setMeasuredDimension(widthSpecSize, finalheight);

        }

    }

    @Override

    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);

        //支持padding,不然padding属性无效

        int paddingLeft = getPaddingLeft();

        int paddingTop = getPaddingTop();

        int paddingRight = getPaddingRight();

        int paddingBottom = getPaddingBottom();

        LogUtil.d("paddingLeft=" + paddingLeft + ",paddingTop=" + paddingTop + ",paddingRight=" + paddingRight + ",paddingBottom=" + paddingBottom);

        int width = getWidth() - paddingLeft - paddingRight;

        int height = getHeight() - paddingTop - paddingBottom;

        canvas.drawText(emptyText, (float) ((width - textRect.width()) * 0.5), (float) (height * 0.5), mPaint);

        canvas.drawBitmap(bitmap, (float) ((width - bitmap.getWidth()) * 0.5), (float) (height * 0.5 - bitmap.getHeight() - 100), mPaint);

    }

    public void setEmptyImage(int id) {

        emptyImage = ContextCompat.getDrawable(context, id);

        BitmapDrawable bitmapDrawable = (BitmapDrawable) emptyImage;

        bitmap = bitmapDrawable.getBitmap();

        //requestLayout();

        invalidate();

    }

    public void setEmptyText(String text) {

        emptyText = text;

        mPaint.getTextBounds(emptyText, 0, emptyText.length(), textRect);

        invalidate();

    }

}

xml引用

<com.wuxiaolong.androidsamples.customview.EmptyVie

    android:id="@+id/emptyView"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:layout_centerInParent="true"

    android:layout_margin="10dp"

    android:padding="10dp"

    app:empty_image="@mipmap/empty_image01"

    app:empty_text="空信息提示语!"/>

布局文件添加schemas声明: xmlns:circle=”http://schemas.android.com/apk/res-auto“ ,这里circle是自定义前缀,名字随便取。但与下面CustomView中自定义属性circle:circle_color=””前缀一致。另外也可以声明 xmlns:circle=”http://schemas.android.com/apk/res/包名“ ,效果是一样的。

参考

《Android群英传》
《Android开发艺术探索》

上一篇 下一篇

猜你喜欢

热点阅读