Days28 自定义Veiw(一)

本文详细介绍了Android中自定义View的基本原理及实现方法,包括View的测量模式、绘图流程及不同类型的自定义View示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注意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.youkuaiyun.com/by317966834/article/details/8773518
http://blog.youkuaiyun.com/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"/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值