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(下)

相关文章
|
5月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
473 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
5月前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
|
5月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
241 65
Android自定义view之网易云推荐歌单界面
|
5月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
574 84
|
5月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
120 3
|
Android开发
Android 自定义View 测量控件宽高、自定义viewgroup测量
Android 自定义View 测量控件宽高、自定义viewgroup测量
554 0
|
XML Android开发 数据格式
Android 中自定义ViewGroup实现流式布局的效果
Android 中自定义ViewGroup实现流式布局的效果
187 0
|
XML Android开发 数据格式
android自定义View&自定义ViewGroup(下)
本篇来看看自定义ViewGroup
221 0
|
XML 前端开发 Android开发
Android自定义View-入门(明白自定义View和自定义ViewGroup)
为什么要自定义View? 主要是Andorid系统内置的View 无法实现我们的 需求,我们需要针对我们的业务需求定制我们想要的 View.
170 0
Android自定义View-入门(明白自定义View和自定义ViewGroup)
|
XML Android开发 数据格式
Android自定义控件(十一)——自定义ViewGroup实现LinearLayout
Android自定义控件(十一)——自定义ViewGroup实现LinearLayout
621 0

热门文章

最新文章