最近学习到自定义view,了解到android对于view的绘制流程,记录一下,供自省。

首先我们自定义view需要继承自android.View,能帮你处理android命名空间的属性,比如说android:layout_width=”match_parent”……,当然如果我们的控件需要自定的控件属性,则需要在attrs.xml文件中预定义样式格式,如下:
attrs.xml

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="MyImageView">            <attr name="src" format="integer"/>    declare-styleable>resources>

然后在使用过程中我们需要在layout文件中声明命名空间:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    xmlns:img="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.example.sosky.skytalk.FriendFragment">    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_marginBottom="85dp">        <com.example.sosky.skytalk.MyImageView        android:layout_width="match_parent"        android:layout_height="match_parent"        img:src="@drawable/road"/>    RelativeLayout>FrameLayout>

这样声明命令空间xmlns:img=”http://schemas.android.com/apk/res-auto”,还可以xmlns:img=”http://schemas.android.com/apk/res/包名”声明命名空间。
接下来我们就需要测量大小和绘制view,下面是我自定义的一个图片控件:

package com.example.sosky.skytalk;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.drawable.BitmapDrawable;import android.graphics.drawable.Drawable;import android.icu.util.Measure;import android.support.v4.content.ContextCompat;import android.util.AttributeSet;import android.view.View;import java.util.jar.Attributes;/** * Created by sOSky on 2017/11/21. */public class MyImageView extends View {    private Paint mBitmapPaint;    private Drawable mDrawable;    private Bitmap mBitmap;    private int mWidth;    private int mHeight;    public MyImageView(Context context){        this(context, null);    }    public MyImageView(Context context, AttributeSet attributeSet){        super(context,attributeSet);        initAtters(attributeSet);        mBitmapPaint = new Paint();        mBitmapPaint.setAntiAlias(true);    }    private void initAtters(AttributeSet attributeSet){        if (attributeSet != null) {            TypedArray array = null;            try {                array = getContext().obtainStyledAttributes(attributeSet,R.styleable.MyImageView);                mDrawable =array.getDrawable(R.styleable.MyImageView_src);                measureDrawable();            }finally {                if (array != null){                    array.recycle();                }            }        }    }    private void measureDrawable(){        if (mDrawable == null) {            throw new RuntimeException("图片不存在!!!");        }        mWidth = mDrawable.getIntrinsicWidth();        mHeight = mDrawable.getIntrinsicWidth();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){        int widthmod = MeasureSpec.getMode(widthMeasureSpec);        int width = MeasureSpec.getSize(widthMeasureSpec);        int heightmod = MeasureSpec.getMode(heightMeasureSpec);        int height = MeasureSpec.getSize(heightMeasureSpec);        setMeasuredDimension(measureWidth(widthmod,width),measureHeight(heightmod,height));    }    @Override    protected void onDraw(Canvas canvas){        if (mBitmap == null){            mBitmap = Bitmap.createScaledBitmap(ImageUtils.drawableToBitmap(mDrawable),getMeasuredWidth(),getMeasuredHeight(),true);        }        canvas.drawBitmap(mBitmap,getLeft(),getTop(),mBitmapPaint);    }    private int measureWidth(int mod, int width){        switch(mod){            case MeasureSpec.UNSPECIFIED:            case MeasureSpec.AT_MOST:                break;            case MeasureSpec.EXACTLY:                mWidth = width;        }        return mWidth;    }    private int measureHeight(int mod , int height){        switch(mod){            case MeasureSpec.UNSPECIFIED:            case MeasureSpec.AT_MOST:            break;            case MeasureSpec.EXACTLY:                mHeight = height;        }        return mHeight;    }}

这里主要关注的两点是,获取开发人员设置属性值和根据父视图测量自身大小。
获取属性值主要是通过Context.obtainStyledAttributes(attribueSet attrs,int styleableid);来获取开发人员设定的属性集中我们需要的属性,在这里我们需要的就是R.styleable.MyImageView。然后对获得的资源进行处理。

测量自身的大小我们需要复写onMeasure(int measurewidth,int measureheight );这两个参数是整个android View绘制的关键。
Measurespec是一个32位整形数据,前两位是测量模式(specMod),后30位父视图的窗口大小(specSize)。
三个测量模式分别是
MeasureSpec.EXACTLY:父视图希望子视图大小应该是由父视图窗口大小来决定,也可以设置成任意的大小。match_parent,具体的数值都是这个模式。
MeasureSpec.AT_MOST:表示子视图只能是specSize中指定的大小,不能超过specSize的大小,一般来说warp_context对应这个模式。
MeasureSpec.UNSPECIFIED:表示子视图能够设定为任意值。使用情况较少,系统开发会用到。
这样我们的子视图就能获取到父视图的窗口大小,再通过开发人员设置的layout属性,就能计算出view的大小。

我当时看到这里产生了一个疑问,这个measurespecwidth和Measurespecheight是从哪里来的。查了资料后,明白了其实是由一个ViewRootImpl类创建的,它是整个view绘制流程的控制类,所有view都是通过它绘制出来的。
根视图的MeasureSpec是由窗口大小和自身LayoutParams决定的,一般来说都是EXACTLY模式和窗口大小,然后他会调用
performMeasure(int childwidthSpec, int childheightSpec){
…..
mView.measure(childwidthSpec,childheightSpec)
……
}
可以看到它是调用的viewGroup的测量函数,遍历当中所有的子view,方式就是调用每一个view的onMeasure()函数。这样子view就获得了测绘模式,父视图大小和自身设定的layoutParams,通过这三个信息,就能计算出具体的大小。

更多相关文章

  1. 详解android:scaleType属性
  2. Android(安卓)关于EditText文字的显示问题 和属性
  3. Android(安卓)根文件系统启动过程。
  4. Android之TextView------属性大全
  5. Android(安卓)学习手札(三) 视图(View)
  6. Android阶段性总结(2011/9/6)
  7. original-package
  8. 【Android】设置tabhost位于底部的三种方法
  9. 使用android快速开发框架afinal的FinalDb操作android sqlite数据

随机推荐

  1. EditText去掉光标下划线以及点击隐藏hint
  2. Android之RecyclerView巧用payload实现局
  3. android 沉浸式布局学习
  4. layout_gravity部局的一些问题
  5. Android连续点击两次Back键退出程序
  6. getSystemService
  7. Android: 渠道号获取
  8. Android刷新加载框架详解
  9. android模拟器使用sdcard
  10. Android(安卓)-- 程序启动画面 Splash