美文网首页
[转] Android CustomView

[转] Android CustomView

作者: 兔斯基第2号 | 来源:发表于2018-08-22 11:50 被阅读0次

    作者:吴小龙
    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有三种:

    • EXACTLY
      精确值模式,父容器已经检测出View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值。它对应代码LayoutParams(控件layout_width属性和layout_height属性)中match_parent和具体数值。

    • AT_MOST
      最大值模式,父容器指定了一个可用大小值,只要不超过父容器允许最大尺寸即可。它对应代码LayoutParams(控件layout_width属性和layout_height属性)中wrap_content。

    • UNSPECIFIED
      父容器不对View有任何限制,要多大给多大,如ListView。

    自定义控件不重写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开发艺术探索》

    相关文章

      网友评论

          本文标题:[转] Android CustomView

          本文链接:https://www.haomeiwen.com/subject/leieiftx.html