android自定义View&自定义ViewGroup(上)

简介: 自定义View&自定义ViewGroup

一般自定义view需要重写的方法

void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
void onSizeChanged(int w, int h, int oldw, int oldh)
void onDraw(Canvas canvas)

一般自定义ViewGroup需要重新的方法

void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
void onSizeChanged(int w, int h, int oldw, int oldh)
void onLayout(boolean changed, int left, int top, int right, int bottom)
void onDraw(Canvas canvas)

可以看出,自定义ViewGroup时必须要重写onLayout()方法(依次排列子view),而自定义View没有子View,所以不需要onLayout(),后面会分别给出自定义View和自定义ViewGroup的例子.
在网上看到一张View的生命周期图,觉得还不错:

941ae15c18cd92c1bfcecbb9967bc175.jpg
先来看下自定义View的作用:
1.在onMeasure中根据测量模式和ViewGroup给出的建议宽和高,计算出自己的宽和高;
2.在onDraw中绘制自己的形态。

绘制自定义View时,如果需要自定义属性,可以在res/values的目录下创建一个attr.xml(名字可以任意起),如:

<declare-styleable name="ColorCircleView">
    <attr name="circle_color" format="color" />
    <attr name="stroke_width" format="integer" />
</declare-styleable>

我们定义了自定义属性名字和取值类型,format类型有
string,color,demension,integer,enum,reference,float,boolean,fraction,flag
然后在构造函数中获得自定义属性:

//获取自定义属性
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColorCircleView);
int cirlceColor = ta.getColor(R.styleable.ColorCircleView_circle_color, Color.RED);
int stokeWidth = ta.getInt(R.styleable.ColorCircleView_stroke_width, 10);
ta.recycle();

在XML中使用自定义属性,如:

<org.ninetripods.mq.ColorCircleView
    android:layout_width="90dp"
    android:layout_height="90dp"
    custom:circle_color="#3F51B5"
    custom:stroke_width="20" />

别忘了在XML最上面加上:xmlns:custom="http://schemas.android.com/apk/res-auto"

接下来看下他的几个常用方法:

  • onMeasure

MeasureSpec是View的一个内部类,一般用到它的MeasureSpecMode(测量模式)和Size(测量大小),其中MeasureSpecMode有以下三种模式:

UNSPECIFIED:

The parent has not imposed any constraint on the child. It can be whatever size it wants.
父view对子view没有任何限制,子view可以是任何大小

EXACTLY:

The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.
父view已经强制设置了子view的大小,一般是MATCH_PARENT和固定值

AT_MOST:

The child can be as large as it wants up to the specified size.
子view限制在一个最大范围内,一般是WARP_CONTENT
最后测量完成后通过setMeasuredDimension(int measuredWidth, int measuredHeight) 将测量的宽高回传给父View。

  • onSizeChanged

在onMeasure()后执行,只有大小发生了变化才会执行onSizeChange

  • onDraw

系统给我们提供了空白的Canvas空白画布,我们可以通过Canvas和Paint来绘制我们想要的图形。
注:onDraw()函数可能会多次调用,所以避免在onDraw()函数中去new 对象

自定义View例子,先上图:

CakeView.png

代码已上传到github:自定义View(饼状图)

核心代码(代码中有详细注释):

public class CakeView extends View {
    //装载的饼状圆数据
    private List<CakeBean> beanList;
    //画圆的矩形
    private RectF mRectF;
    //右边的小矩形
    private RectF iRectF;
    private Paint mPaint;
    private int mRWidth, mRHeight;
    private float rotateDegree;//每个圆弧的起始角度
    private float sumValue = 0;//所有值的和
    private float diameter;//圆的直径
    private float textY;//绘制文字的Y坐标
    private float mRectHeight = 40;//矩形高度
    private float mRectWidth = 80;//矩形宽度
    private float mMargin = 40;//矩形和圆的距离
    private Context mContext;

    public CakeView(Context context) {
        //CakeView cakeView=new CakeView(context);
        // 在代码中new CakeView()会调用这个构造函数
        this(context, null);
    }

    public CakeView(Context context, AttributeSet attrs) {
        //InflateLayoutManager时会调用这个构造函数
        this(context, attrs, 0);
    }

    public CakeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init();
   }

    private void init() {
        beanList = new ArrayList<>();
        mRectF = new RectF();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //MeasureSpec封装了父View传递给子View的布局要求
        //宽度测量模式
        int wMode = MeasureSpec.getMode(widthMeasureSpec);
        //宽度测量值
        int wSize = MeasureSpec.getSize(widthMeasureSpec);
        //高度测量模式
        int hMode = MeasureSpec.getMode(heightMeasureSpec);
        //高度测量值
        int hSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (wMode) {
            case MeasureSpec.EXACTLY:
                //相当于match_parent或者一个具体值
                mRWidth = wSize;
                break;
            case MeasureSpec.AT_MOST:
                // 相当于wrap_content ,需要手动测量大小,这里先写死大小
                mRWidth = (int) DpUtil.dp2px(mContext, 400f);
                break;
            case MeasureSpec.UNSPECIFIED:
                //很少会用到
                break;
            default:
                break;
        }
        switch (hMode) {
            case MeasureSpec.EXACTLY:
                //相当于match_parent或者一个具体值
                mRHeight = hSize;
                break;
            case MeasureSpec.AT_MOST:
               // 相当于wrap_content ,需要手动测量大小,这里先写死大小
                mRHeight = (int) DpUtil.dp2px(mContext, 200f);
                break;
            case MeasureSpec.UNSPECIFIED:
                //很少会用到
                break;
            default:
                break;
        }
        //存储测量好的宽和高
        setMeasuredDimension(wSize, hSize);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        diameter = Math.min(mRWidth, mRHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置圆形绘制的范围
        mRectF.set(0, 0, diameter, diameter);
        //画布中心X坐标向右移动(控件宽度-圆直径)之差的八分之一的距离
        //画布中心Y坐标向下移动(控件宽度-圆直径)之差的二分之一的距离
        canvas.translate((mRWidth - diameter) / 8, (mRHeight - diameter) / 2);
        if (beanList.size() > 0 && Float.compare(sumValue, 0.0f) != 0) {
            for (int i = 0; i < beanList.size(); i++) { 
               CakeBean bean = beanList.get(i);
                //画圆弧
                mPaint.setColor(bean.mColor);
                canvas.drawArc(mRectF, rotateDegree, bean.degree, true, mPaint);
                rotateDegree += bean.degree;
                //画矩形和文字
                drawRectAndText(canvas, bean);
            }
        }
    }

    private void drawRectAndText(Canvas canvas, CakeBean bean) {
        iRectF = new RectF();
        //设置画矩形的范围
        float left = diameter + mMargin;
        float right = diameter + mMargin + mRectWidth;
        float bottom = textY + mRectHeight;
        iRectF.set(left, textY, right, bottom);
        canvas.drawRect(iRectF, mPaint);
        //设置颜色
        mPaint.setColor(Color.BLACK);
        //设置文字大小
        mPaint.setTextSize(30);
        //画文字
        canvas.drawText(bean.name + "(" + new DecimalFormat(".00").format(bean.value / sumValue * 100) + "%)", right + 10, textY + 30, mPaint);
        textY += mRectHeight;
    }

    /**
     * 饼状图添加数据
     * 
    * @param beans CakeBean数据
     */
    public void setData(List<CakeBean> beans) {
        if (beans == null || beans.size() <= 0) return;
        for (int i = 0; i < beans.size(); i++) {
            CakeBean bean = beans.get(i);
            sumValue += bean.value;
        }
        for (int i = 0; i < beans.size(); i++) {
            CakeBean bean = beans.get(i);
            bean.degree = bean.value / sumValue * 360;
            beanList.add(bean);
        }
        invalidate();
    }

    /**
     * @param startDegree 设置起始角度
     */    public void setStartDegree(float startDegree) {
        this.rotateDegree = startDegree;
        invalidate();
    }}

自定义View的使用先到这里,自定义ViewGroup接下篇
android自定义View&自定义ViewGroup(下)

相关文章
|
1月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
27 1
|
1月前
|
XML 前端开发 Android开发
Android:UI:Drawable:View/ImageView与Drawable
通过本文的介绍,我们详细探讨了Android中Drawable、View和ImageView的使用方法及其相互关系。Drawable作为图像和图形的抽象表示,提供了丰富的子类和自定义能力,使得开发者能够灵活地实现各种UI效果。View和ImageView则通过使用Drawable实现了各种图像和图形的显示需求。希望本文能为您在Android开发中使用Drawable提供有价值的参考和指导。
40 2
|
1月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
1月前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
41 5
|
2月前
|
缓存 数据处理 Android开发
在 Android 中使用 RxJava 更新 View
【10月更文挑战第20天】使用 RxJava 来更新 View 可以提供更优雅、更高效的解决方案。通过合理地运用操作符和订阅机制,我们能够轻松地处理异步数据并在主线程中进行 View 的更新。在实际应用中,需要根据具体情况进行灵活运用,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在 Android 中使用 RxJava 更新 View 的技巧和方法,为开发高质量的 Android 应用提供有力支持。
|
2月前
|
缓存 调度 Android开发
Android 在子线程更新 View
【10月更文挑战第21天】在 Android 开发中,虽然不能直接在子线程更新 View,但通过使用 Handler、AsyncTask 或 RxJava 等方法,可以实现子线程操作并在主线程更新 View 的目的。在实际应用中,需要根据具体情况选择合适的方法,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在子线程更新 View 的技巧和方法,为开发高质量的 Android 应用提供支持。
43 2
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
29 2
|
Android开发
自定义android 4.0以上的对话框风格
做个笔记,这里是Dialog的风格,如果是用AlertDialog创建的,不能直接用。在styles.xml的写法: 22sp @color/font_green 1 true @st...
709 0
|
20天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
40 19