Days28 自定义Veiw(一)

来源:互联网 发布:vb有什么用 编辑:程序博客网 时间:2024/06/05 07:56

注意1:
1、关键部分:
Drawing the layout is a two pass process: a measure pass and a layout pass.
所以一个view执行OnDraw时最关键的是measure和layout。其实这很好理解的,一个view需要绘制出来,那么必须知道他要占多大的空间也就是measure,还得知道在哪里绘制,也就是把view放在哪里即layout。把这两部分掌握好也就可以随意自定义view了。至于viewGroup中如何绘制就参考官方文档,其实就是一个分发绘制,直到child是一个view自己进行绘制。
2、重写一个view一般情况下只需要重写onDraw()方法。那么什么时候需要重写onMeasure()、onLayout()、onDraw() 方法呢,这个问题只要把这几个方法的功能弄清楚就应该知道怎么做了。
①、如果需要绘制View的图像,那么需要重写onDraw()方法。(这也是最常用的重写方式。)
②、如果需要改变view的大小,那么需要重写onMeasure()方法。
③、如果需要改变View的(在父控件的)位置,那么需要重写onLayout()方法。
④、根据上面三种不同的需要你可以组合出多种重写方案。
3、按类型划分,自定义View的实现方式可分为三种:自绘控件、组合控件、以及继承控件。
注意2:
View的三种测量模式
ViewGroup会为childView指定测量模式,下面简单介绍下三种测量模式:
1、EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;
2、AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
3、UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。
【备注】:
上面的每一行都有一个一般,意思上述不是绝对的,对于childView的mode的设置还会和ViewGroup的测量mode有一定的关系;当然了,这是第一篇自定义ViewGroup,而且绝大部分情况都是上面的规则,所以为了通俗易懂,暂不深入讨论其他内容。
注意3:
三种类型的自定义View
(1)自绘控件:
1、概念:自绘控件的意思就是,这个View上所展现的内容全部都是自己绘制出来的。
2、自绘控件的步骤:
1、绘制View :
绘制View主要是onDraw()方法中完成。通过参数Canvas来处理,相关的绘制主要有drawRect、drawLine、drawPath等等。
Canvas绘制的常用方法:
drawColor() 填充颜色
drawLine() 绘制线
drawLines() 绘制线条
drawOval() 绘制圆
drawPaint()
drawPath() 绘制路径
drawPicture() 绘制图片
drawPoint() 绘制点
drawPoints() 绘制点
drawRGB() 填充颜色
drawRect() 绘制矩形
drawText() 绘制文本
drawTextOnPath() 在路径上绘制文本
2、刷新View :(刷新view的方法这里主要有:)
invalidate(int l,int t,int r,int b)
刷新局部,四个参数分别为左、上、右、下
invalidate()
整个view刷新。执行invalidate类的方法将会设置view为无效,最终重新调用onDraw()方法。
invalidate()是用来刷新View的,必须是在UI线程中进行工作。在修改某个view的显示时,调用invalidate()才能看到重新绘制的界面。invalidate()的调用是把之前的旧的view从主UI线程队列中pop掉。
invalidate(Rect dirty)
刷新一个矩形区域
3、控制事件:(例如处理以下几个事件)
onSaveInstanceState() 处理屏幕切换的现场保存事件
onRestoreInstanceState() 屏幕切换的现场还原事件
onKeyDown() 处理按键事件
onMeasure() 当控件的父元素正要放置该控件时调用
(2)组合控件:
概念:
组合控件的意思就是,不需要自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。
(3)继承控件
概念:
继承控件的意思就是,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。
案例一:
自定义ImageView控件:自定义ImageView控件继承ImageView
备注:
短句:使用短句生成构造方法 ViewConstructor
自定义ImageView控件类

public class SmartImageView extends ImageView {    private Handler handler = new Handler(){        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what){                case 1:                //自定义ImageView类对象调用此方法                    setImageBitmap((Bitmap) msg.obj);                    break;                case 2:                    setImageResource(msg.arg1);                    break;            }        }    };    public SmartImageView(Context context) {        this(context, null);    }    public SmartImageView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public SmartImageView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    public void setSmartImageView(final String url, final int resId){        new Thread(){            @Override            public void run() {                super.run();                Message msg = Message.obtain();                byte[] data = OkHttpUtils.getByteArrayByUrl(url);                if(data != null){                    Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);                    msg.what = 1;                    msg.obj = bitmap;                }else{                    msg.what = 2;                    msg.arg1 = resId;                }                handler.sendMessage(msg);            }        }.start();    }}

xml:

<com.sign.days28selfview01.SmartImageView        android:id="@+id/ivPic"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:scaleType="fitXY"        android:src="@mipmap/ic_launcher"        android:contentDescription="@string/app_name"/>

MainActivity:

 public void download(View view) { //调用自定义ImageView控件类的设置图片方法        ivPic.setSmartImageView(url,R.mipmap.ic_launcher);    }

案例二:自定义TextView控件
自定义TextView控件类

public class Rular extends TextView {    public Rular(Context context) {        this(context, null);    }    public Rular(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public Rular(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);//       设置Ruler中的文字位置。相当于属性中的android:gravity        setGravity(Gravity.BOTTOM);    }    /**     * 重写该方法,实现自己“绘制”控件的样式     * 该方法无须手动调用, 系统在绘制该控件时会自动调用该方法绘制该控件     * @param canvas     */    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);//      Paint(画笔        Paint p = new Paint();//      设置画笔颜色        p.setColor(Color.YELLOW);//        getWidth()为获取控件的宽度        int w = getWidth()/10;        int top = 1;        for (int i = 0;i<10;i++){//            canvas的drawRect(float left,float top,float right,float bottom,Paint paint)//            参数分别为左上右下(顺时针)(左上右下离控件的x,y轴的距离),画笔对象//            Canvas(画布)            canvas.drawRect(i,top,w+i*w,top+w,p);        }//        for循环语句相当于//        canvas.drawRect(0,top,getWidth(),top+w,p);    }}

布局文件:
MainActivity添加控件时会自动找到控件对应的类,并自动调用其中方法对控件进行相应设置

  <com.sign.days28selfview02ruler.Rular        android:layout_width="40dp"        android:layout_height="40dp"        android:background="#0cf"        android:text="0cm" />    <com.sign.days28selfview02ruler.Rular        android:layout_width="40dp"        android:layout_height="40dp"        android:background="#acc"        android:text="1cm" />    <com.sign.days28selfview02ruler.Rular        android:layout_width="40dp"        android:layout_height="40dp"        android:background="#f0d"        android:text="2cm" />    <com.sign.days28selfview02ruler.Rular        android:layout_width="40dp"        android:layout_height="40dp"        android:background="#ae0"        android:text="3cm" />    <com.sign.days28selfview02ruler.Rular        android:layout_width="40dp"        android:layout_height="40dp"        android:background="#0d0"        android:text="4cm" />

案例三:
组合控件:
将progressbar与button组合在一起,点击button按钮后,发送一个消息,在主线程中更新progressBar进度并判断是否到100%若不是继续发送消息并根据进度设置Button上文字,若是则不再发送消息并设置Button文字为下载完成
(实际上是ProgressBar在下面,Button在上面,看到的ProgressBar进度实际上是ProgressBar的边框在更新)
备注:在Android的layout样式定义中,可以使用xml文件方便的实现,有时候为了模块的复用,使用include标签可以达到此目的。
在activity_main中:

<include layout="@layout/my_layout"></include>

引入my_layout布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="70dp"    android:orientation="vertical"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.sign.days28selfview03progressbutton.MainActivity">    <ProgressBar        android:id="@+id/pbShow"        style="?android:attr/progressBarStyleHorizontal"        android:layout_width="match_parent"        android:layout_height="25dp"        android:max="100"        android:progress="0"        android:progressDrawable="@drawable/my_back"/>    <Button        android:id="@+id/btn"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:textColor="#0fc"        android:textSize="16sp"        android:onClick="start"        android:text="start"/></LinearLayout>

此处注意:
progressbar的progressDrawable属性为一个xml文件
还不太理解??????

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">    <item>        <!--<clip            android:drawable="@drawable/btn_back_pre"            android:gravity="left">        </clip>-->        <clip            android:clipOrientation="horizontal"            android:gravity="left"            android:drawable="@drawable/pb_back">        </clip>    </item></layer-list>

此处再注意:
第二个clip节点的drawable为名为pb_back的xml文件
**备注:**Android中常常使用shape来定义控件的一些显示属性
参考自:
http://blog.csdn.net/by317966834/article/details/8773518
http://blog.csdn.net/harvic880925/article/details/41850723
pd_back.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android">    <!--设置渐变    startColor centerColor endColor 起始、中间、结束颜色    type 渐变模式 linear 线性渐变 radial 径向渐变 需要指定半径-->    <gradient        android:centerColor="#0cd"        android:centerX="0"        android:centerY="0"        android:endColor="#f00"        android:gradientRadius="50"        android:startColor="#0f0"        android:type="radial" />    <!--设置填充-->    <solid android:color="@android:color/holo_blue_bright" />    <!--设置dashGap和dashWidth则边框显示为虚线,dashGap为破折线空隙,dashWidth为破折线长度    dashGap为0dp时为实线 dashWidth需要指定-->    <!--width为边框的宽度-->    <stroke        android:width="5dp"        android:color="@android:color/holo_orange_light"        android:dashGap="2dp"        android:dashWidth="0dp" />    <!--设置矩形的圆角半径-->    <corners android:radius="5dp" /></shape>

案例四
自定义ProgressView继承View
效果:一个逐渐转动变大的扇形
在values目录下添加attrs.xml文件

<resources>    <!--attrs.xml文件   该文件是定义属性名和格式的地方,    declare-styleable是给自定义控件添加自定义属性用的    需要用<declare-styleable name="ToolBar"></declare-styleable>包围所有属性。    其中name为该属性集的名字,主要用途是标识该属性集-->    <declare-styleable name="MyProgressView">        <attr name="sweepStep" format="integer"/>        <attr name="padding" format="integer"/>        <attr name="startAngle" format="integer"/>        <attr name="circleColor" format="color|reference"/>        <attr name="sweepColor" format="color|reference"/>    </declare-styleable></resources>

自定义ProgressView控件

public class MyProgressView extends View {    private static final int DEFAULT_WIDTH = 100;    private static final int DEFAULT_HEIGHT = 100;    private int sweepAngle = 0;//扇形扫过的角度    private int sweepStep = 5;//扇形添加的角度    private int padding = 5;//设置扇形与圆之间距离    private int startAngle = -90;//设置扇形开始的角度    private int circleColor = Color.GREEN;//圆的背景颜色    private int sweepColor = Color.BLUE;//扇形的颜色    public MyProgressView(Context context) {        this(context, null);    }    public MyProgressView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MyProgressView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);/** * TypedArray实例是个属性的容器,context.obtainStyledAttributes()方法返回得到 *  context.obtainStyledAttributes(AttributeSet set,@StyleableRes int[] attrs) *  set :节点的属性集合 *  attrs :包含attr节点的StyleableRes节点name标识 */        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyProgressView);        if(array != null){//            分别获取MyProgress节点中的各个属性            sweepStep = array.getInteger(R.styleable.MyProgressView_sweepStep,sweepStep);            startAngle = array.getInteger(R.styleable.MyProgressView_startAngle,startAngle);            padding = array.getInteger(R.styleable.MyProgressView_padding,padding);            sweepColor = array.getInteger(R.styleable.MyProgressView_sweepColor,sweepColor);            circleColor = array.getInteger(R.styleable.MyProgressView_circleColor,circleColor);//          array复用  作用:在TypedArray后调用recycle主要是为了缓存。当recycle被调用后,这就说明这个对象从现在可以被重用了            array.recycle();        }    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        Paint p = new Paint();//        消除锯齿        p.setAntiAlias(true);//        画圆//        设置圆的颜色        p.setColor(circleColor);//        前两个参数分别确定圆心x、y坐标,第三个参数为圆的半径,第四个参数为画笔        canvas.drawCircle(getWidth()/2,getWidth()/2,getWidth()/2,p);//        画扇形//        重新设置画笔颜色为扇形颜色        p.setColor(sweepColor);        /**         * public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)         oval :指定圆弧的外轮廓矩形区域。         startAngle: 圆弧起始角度,单位为度。0度为水平向左         sweepAngle: 圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。         useCenter: 如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形。         paint: 画笔         */        canvas.drawArc(new RectF(padding,padding,getWidth()-padding,getWidth()-padding),startAngle,sweepAngle,true,p);//        每画完一个扇形累加已扫过的角度        sweepAngle += sweepStep;//        当已扫过的角度为360时,将已扫过的角度置空        sweepAngle= sweepAngle >= 360 ? 0:sweepAngle;//         刷新整个界面(每次画完一个扇形就刷新整个界面,看起来就像扇形在更新进度)        invalidate();    }    /**     * 设置控件自身的宽和高     * @param widthMeasureSpec     * @param heightMeasureSpec     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        switch (widthMode){            case MeasureSpec.AT_MOST:                widthSize = DEFAULT_WIDTH;                heightSize = DEFAULT_HEIGHT;                break;            case MeasureSpec.EXACTLY:                widthSize = heightSize = Math.min(widthSize,heightSize);                break;            case MeasureSpec.UNSPECIFIED:                break;        }//      这个方法决定了当前View的大小        setMeasuredDimension(widthSize,heightSize);    }}

activity_main文件:
备注: xmlns:app=”http://schemas.android.com/apk/res-auto”
android中xml中有些控件的属性里面有 “app:..” ,此处的app:是什么意思?和一般的android:有什么区别?
答:这两个是声明的不同的命名空间,android的是系统的,app是自定义的。Android自定义控件的属性,在xml中使用自己自定义的attr的时候,其中有一步就是要自定义一个xml的命名空间后然后再给自定义属性赋值。
参考自:http://zhidao.baidu.com/link?url=628WYzFtOkZXw2aAau3vV8yZhBWN6OApXGFxn5IBTmy7OUUekmOeaQxpWpc9RLr-ecsYG-QzpA1Tz7ncbrEApC5RyYwRIFRvBzXOteg0aky

 <!--注意:此处使用的为wrap_content,在MyProgressView中会走到case MeasureSpec.AT_MOST    设置宽和高为100,但其为100px,所以与下一个控件的大小100dp并不相等-->    <com.sign.days28selfviw04progress.MyProgressView        android:layout_width="wrap_content"        android:layout_height="wrap_content"/>    <com.sign.days28selfviw04progress.MyProgressView        android:layout_width="100dp"        android:layout_height="100dp"        app:circleColor="#0f0"        app:sweepColor="#0cc"        app:padding="26"        app:startAngle="0"        app:sweepStep="5"/>
0 0