android实现自定义view

来源:互联网 发布:java锁的种类 编辑:程序博客网 时间:2024/05/06 04:11

很多朋友看到这简直就是噩梦,因为自定Vewi的实现可以很复杂,也可以很简单,我们就从简单的开始,毕竟自己也是初学者
先在value文件家下面新建attrs.xml文件,内容如下

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="MyView">        <attr name="textSize" format="integer"></attr>        <attr name="textColor" format="reference|color"></attr>    </declare-styleable></resources>

在上面我们自定义了属性,一个size,格式为整形,一Color为引用型,以及color,具体就不解释了,大家可以百度,然后我们新建class对象,继承View,具体如下

package com.example.fang.myapplication;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.util.Log;import android.view.View;/** * Created by fang on 2017/11/10. * 自定义View的实现 */public class MyView extends View {    String TAG = "main";    int textColor;    int textSize;    Paint paint;    RectF rectf;    public MyView(Context context) {        this(context, null);    }    public MyView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        TypedArray type = getResources().obtainAttributes(attrs, R.styleable.MyView);        int count = type.getIndexCount();        for (int i=0;i<count;i++){            int index=type.getIndex(i);            switch(index){                case R.styleable.MyView_textColor:{               textColor=type.getColor(index,-1);                    break;                }                case R.styleable.MyView_textSize:{                    textSize=type.getInteger(index,-1);                }            }        }        intiialData();    }    private void intiialData() {        paint=new Paint();        paint.setColor(textColor);        paint.setTextSize(textSize);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        int min= (int) Math.min(getWidth(),getHeight());        canvas.drawCircle(min/2,min/2,min/2,paint);    }}

然后在将我们自定义的View加到主布局中,内容如下

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    xmlns:user="http://schemas.android.com/apk/res/com.example.fang.myapplication"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.example.fang.myapplication.MainActivity">    <com.example.fang.myapplication.MyView        android:layout_width="200dp"        android:layout_height="200dp"        user:textColor="@color/colorAccent"        user:textSize="20" /></LinearLayout>

需要注意的是上面的命名空间 xmlns:user=”http://schemas.android.com/apk/res/com.example.fang.myapplication”,这里res后面的是我的程序的完整路径名,你们在书写时,也要写上自己的完整路径名,why?
because我们自定义了属性,我们当然要通过命名空间加载,这里我们的命名空间为user,就像android都是android一样,之后我们运行
这里写图片描述
那么现在有两个问题,上面我们myView的布局宽高使用的是实际值
ps:android:layout_width=”200dp , android:layout_height=”200dp” 那么有时我们考虑到程序的兼容性,会将值设为wrap_content,那么我们将值设为wrap_content看会出现什么情况,下面是运行结果:
这里写图片描述
我去变大了,注意这里我们在绘制园的时候使用的是getWidth和getheight,刚才在测试发现,如果我们设实际值,园就会按实际值显示,而用wrap_content就会显示为整个屏幕的大小,具体原因为什么,这就涉及到底层的实现了,我们就不讨论了(其实是没那水平),大家可以百度一下,下面我们来看看应对方法,我们改下代码

package com.example.fang.myapplication;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.util.Log;import android.view.View;/** * Created by fang on 2017/11/10. * 自定义View的实现 */public class MyView extends View {    String TAG = "main";    int textColor;    int textSize;    Paint paint;    RectF rectf;    int width;    int height;//View的宽高    int radio=100;//园的半径    //我们将圆的半径设为100,如果使用Wrap_content就为200大小    public MyView(Context context) {        this(context, null);    }    public MyView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        TypedArray type = getResources().obtainAttributes(attrs, R.styleable.MyView);        int count = type.getIndexCount();        for (int i = 0; i < count; i++) {            int index = type.getIndex(i);            switch (index) {                case R.styleable.MyView_textColor: {                    textColor = type.getColor(index, -1);                    break;                }                case R.styleable.MyView_textSize: {                    textSize = type.getInteger(index, -1);                }            }        }        intiialData();    }    private void intiialData() {        paint = new Paint();        paint.setColor(textColor);        paint.setTextSize(textSize);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        int min = (int) Math.min(width, height);        Log.d(TAG, "width="+width);        canvas.drawCircle(min / 2, min / 2, min / 2, paint);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int mode1 = MeasureSpec.getMode(widthMeasureSpec);        int size = MeasureSpec.getSize(widthMeasureSpec);        int mode2= MeasureSpec.getMode(heightMeasureSpec);        int size1 =MeasureSpec.getSize(heightMeasureSpec);        if(mode1==MeasureSpec.EXACTLY){//这个代表的是match_parent            width=size;//当我们在定义布局是采用的是match_parent时,我们就采用采用实际大小        }else{            width=radio*2;        }        if(mode2==MeasureSpec.EXACTLY){//这个代表的是match_parent            height=size;//当我们在定义布局是采用的是match_parent时,我们就采用采用实际大小        }else{            height=radio*2;        }        setMeasuredDimension(width,height);    }}

我们重写了Onmeasure方法,在这里调用了MeasureSpec对Mode和Size进行判断,如果是EXACTLY(代表额是match_parent),反之对应的是 MeasureSpec.AT_MOST(对应wrap_content)和MeasureSpec.UNSPECIFIED(对应于不对View有任何限制),在里面我们定义了园的半径大小,如果是内容填充,就是Radio*2了;这个就不要问我为什么了吧,半径=半径*半径(开玩笑的),继续看啊
另外,还有个问题就是这鬼东西不居中,我们首先想到是通过gravity进行控制

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    xmlns:user="http://schemas.android.com/apk/res/com.example.fang.myapplication"    android:layout_width="match_parent"    android:layout_height="match_parent" android:orientation="vertical"    tools:context="com.example.fang.myapplication.MainActivity">    <com.example.fang.myapplication.MyView        android:layout_width="wrap_content"        android:layout_gravity="center_horizontal"        android:layout_height="wrap_content"        user:textColor="@color/colorAccent"        user:textSize="20" /></LinearLayout>

这样进行控制也可以,其实我们也可以通过修改代码View的代码进行实现,我们来实现一下吧
修改主布局代码

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    xmlns:user="http://schemas.android.com/apk/res/com.example.fang.myapplication"    android:layout_width="match_parent"    android:layout_height="match_parent" android:orientation="vertical"    tools:context="com.example.fang.myapplication.MainActivity">    <com.example.fang.myapplication.MyView        android:layout_width="match_parent"        android:layout_height="wrap_content"        user:textColor="@color/colorAccent"        user:textSize="20" /></LinearLayout>

在上面我们取消了通过gravity的设置,然后我们把width改为了match_parent,然后我们看下View的实现代码

package com.example.fang.myapplication;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.RectF;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.util.Log;import android.view.View;/** * Created by fang on 2017/11/10. * 自定义View的实现 */public class MyView extends View {    String TAG = "main";    int textColor;    int textSize;    Paint paint;    RectF rectf;    int width;    int height;//View的宽高    int radio=100;//园的半径    //我们将圆的半径设为100,如果使用Wrap_content就为200大小    Path path;    public MyView(Context context) {        this(context, null);    }    public MyView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        TypedArray type = getResources().obtainAttributes(attrs, R.styleable.MyView);        int count = type.getIndexCount();        for (int i = 0; i < count; i++) {            int index = type.getIndex(i);            switch (index) {                case R.styleable.MyView_textColor: {                    textColor = type.getColor(index, -1);                    break;                }                case R.styleable.MyView_textSize: {                    textSize = type.getInteger(index, -1);                }            }        }        intiialData();    }    private void intiialData() {        paint = new Paint();        paint.setColor(textColor);        paint.setTextSize(textSize);        path=new Path();    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        invaliallayout();        int x= (int) ((rectf.left+rectf.right)/2);        int min = (int) Math.min(width, height);        Log.d(TAG, "min="+min+"width="+x);        canvas.drawCircle(x, min / 2, min / 2, paint);    }    private void invaliallayout() {        int size=radio*2;        int left=(width-size)/2;        int top=(height-size)/2;        int right=left+size;        int bottom=top+size;        rectf=new RectF(left,top,right,bottom);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int mode1 = MeasureSpec.getMode(widthMeasureSpec);        int size = MeasureSpec.getSize(widthMeasureSpec);        int mode2= MeasureSpec.getMode(heightMeasureSpec);        int size1 =MeasureSpec.getSize(heightMeasureSpec);      //  MeasureSpec.AT_MOST,MeasureSpec.UNSPECIFIED        if(mode1==MeasureSpec.EXACTLY){//这个代表的是match_parent            width=size;//当我们在定义布局是采用的是match_parent时,我们就采用采用实际大小        }else{            width=radio*2;        }        if(mode2==MeasureSpec.EXACTLY){//这个代表的是match_parent            height=size;//当我们在定义布局是采用的是match_parent时,我们就采用采用实际大小        }else{            height=radio*2;        }        setMeasuredDimension(width,height);    }}

我们发现代码增加了一个实现RectF的方法,由于我们已经通过width和height拿到了view布局中的宽高,然后通过运算将Rectf放在View的中间, int size=radio*2;
int left=(width-size)/2;
int top=(height-size)/2;
int right=left+size;
int bottom=top+size;
是实现的关键,通过实际(View的大小-直径)/2,我们可以求出左边的边距,同样上面也一样,right和bottom通过加法就能实现,当然你如果想通过 int left=(width-size)/2;这种方法实现也可以,多计算了一次,没这必要,既然Rectf居中了,我们在draw中通过计算得出Rectf的中心坐标,将值传给绘制圆的x坐标,就能实现圆的居中了。
有图有真相:
这里写图片描述

到此为止!!!
原创粉丝点击