zoukankan      html  css  js  c++  java
  • Android-自定义控件-继承View与ViewGroup的初步理解

    继承View需要走的流程是:

                1.构造实例化, public ChildView(Context context, @Nullable AttributeSet attrs)

                2.测量自身的高和宽onMeasure-->setMeasuredDimension(宽,高)

                3.onDraw绘制,需要X轴,Y轴

    继承ViewGroup需要走的流程是:

                1.构造实例化, public ChildView(Context context, @Nullable AttributeSet attrs)

                2.onMeasure测量子控件的高和宽,子控件.measure(宽,高),而自己的高和宽交给父控件去测量,因为我是父控件的子控件

                3.onLayout给子控件排版指定位置

    布局文件,指定自定义类:

    <!-- 继承View 与 继承ViewGroup的初步理解 -->
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:myswitch="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="400dp"
        android:layout_height="500dp">
    
        <!-- 爷爷类,爷爷类有一个孩子(爸爸类) -->
        <custom.view.upgrade.view_viewgroup_theory.GrandpaViewGroup
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:background="#3300cc"
            android:layout_centerInParent="true"
            > <!--
                虽然android:layout_centerInParent="true"属性可以去居中
                但这是Android RelativeLayout 对爷爷类进行了居中的排版固定位置处理
             -->
    
            <!-- 爸爸类,爸爸类有一个孩子(孩子类) -->
            <custom.view.upgrade.view_viewgroup_theory.FatherViewGroup
                android:layout_width="200dp"
                android:layout_height="200dp"
                android:background="#00ccff">
    
                <!-- 孩子类目前不包含孩子 -->
                <custom.view.upgrade.view_viewgroup_theory.ChildView
                    android:layout_width="100dp"
                    android:layout_height="100dp"
                    android:background="#cc3300"/>
    
            </custom.view.upgrade.view_viewgroup_theory.FatherViewGroup>
    
        </custom.view.upgrade.view_viewgroup_theory.GrandpaViewGroup>
        
    
    </RelativeLayout>

    爷爷类,GrandpaViewGroup:

    package custom.view.upgrade.view_viewgroup_theory;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    import android.view.ViewGroup;
    
    /**
     * 爷爷类,爷爷类有自己的孩子(爸爸类) ----> 爸爸类有自己的孩子(孩子类)
     */
    public class GrandpaViewGroup extends ViewGroup {
    
        private static final String TAG = GrandpaViewGroup.class.getSimpleName();
    
        /**
         * 此两个参数的构造方法,用于在xml布局指定初始化,并传入xml中的所有属性
         * @param context
         * @param attrs
         */
        public GrandpaViewGroup(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        // 爸爸类
        private View fatherViewGroup;
    
        /**
         * 当xml布局完成加载后,调用此方法
         */
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            // 获取子控件(爸爸类)
            fatherViewGroup = getChildAt(0);
        }
    
        /**
         * 由于当前是ViewGroup所以测量子控件的宽和高
         * ,如果当前是View就是此类自己的高和宽
         * @param widthMeasureSpec
         * @param heightMeasureSpec
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            // 测量子控件(爸爸类)的宽和高
            // 宽和高就是 爸爸类在xml布局中设置的值
            fatherViewGroup.measure(fatherViewGroup.getLayoutParams().width, fatherViewGroup.getLayoutParams().height);
    
            // 想获取测量后子控件(爸爸类)的高和宽,是无法获取到的,因为子控件(爸爸类)是ViewGroup,拿到测量后的高和宽需要 View-->setMeasuredDimension()
            // 测试下:子控件(爸爸类)的高和宽
            Log.d(TAG, "fatherViewGroup.getMeasuredWidth():" + fatherViewGroup.getMeasuredWidth() +
                             " fatherViewGroup.getMeasuredHeight():" + fatherViewGroup.getMeasuredHeight());
        }
    
        /**
         * 排版 指定位置,只给子控件指定位置的
         * @param changed 当发生改变
         * @param l 左边线距离左边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
         * @param t 上边线距离上边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
         * @param r 右边线距离左边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
         * @param b 底边线距离上边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
         *
         * 父控件必须排版了layout此类的位置,此onLayout方法才会执行,否则不执行
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            // 测试下:获取自身当前GrandpaViewGroup测量后的宽和高
            int measuredWidth = getMeasuredWidth();
            int measuredHeight = getMeasuredHeight();
            Log.d(TAG, "measuredWidth:" + measuredWidth + " measuredHeight:" + measuredHeight);
    
            // 给子控件(爸爸类)指定位置
            // Y轴与X轴移动计算一样,X计算:移动X轴方向,得到自身(GrandpaViewGroup爷爷类)宽度的一半 减 子控件(爸爸类)的一半就居中了
            int fatherL = (measuredWidth / 2) - (fatherViewGroup.getLayoutParams().width / 2);
            int fatherT = (measuredHeight / 2) - (fatherViewGroup.getLayoutParams().height / 2);
    
            // L位置增加了,R位置也需要增加
            int fatherR = fatherViewGroup.getLayoutParams().width + fatherL;
            int fatherB = fatherViewGroup.getLayoutParams().height + fatherT;
            fatherViewGroup.layout(fatherL, fatherT, fatherR, fatherB);
        }
    
        /**
         * 为什么继承了ViewGroup就不需要onDraw绘制了,因为绘制都是在View中处理
         * 继承了ViewGroup需要做好测量-->排版固定位置就好了,绘制是交给View去处理
         */
    }

    爸爸类,FatherViewGroup:

    package custom.view.upgrade.view_viewgroup_theory;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    import android.view.ViewGroup;
    
    /**
     * 爸爸类,爸爸类有自己的孩子
     */
    public class FatherViewGroup extends ViewGroup {
    
        private static final String TAG = FatherViewGroup.class.getSimpleName();
    
        /**
         * 此两个参数的构造方法,用于在xml布局指定初始化,并传入xml中的所有属性
         * @param context
         * @param attrs
         */
        public FatherViewGroup(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        // 子控件(孩子类)
        private View childView;
    
        /**
         * 当xml布局完成加载后,调用此方法
         */
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
    
            childView = getChildAt(0);
        }
    
        private int w;
        private int h;
    
        /**
         * 测量子控件(孩子类)的高和宽
         * @param widthMeasureSpec 可以转变为模式,也可以转变为父类给当前自己传递过来的宽
         * @param heightMeasureSpec 可以转变为模式,也可以转变为父类给当前自己传递过来的高
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            // 可以获取模式
            MeasureSpec.getMode(widthMeasureSpec);
            MeasureSpec.getMode(heightMeasureSpec);
    
            /**
             * 可以获取父类(爷爷类)在测量方法--->
             * fatherViewGroup.measure(fatherViewGroup.getLayoutParams()
             * .width, fatherViewGroup.getLayoutParams().height);
             * 传递过来的高和宽
             */
            w = MeasureSpec.getSize(widthMeasureSpec);
            h = MeasureSpec.getSize(heightMeasureSpec);
            Log.d(TAG, " w:" + w + " h:" + h);
    
            // 开始测量子控件(孩子类)
            // 宽高都是孩子类在xml布局设置的宽高
            childView.measure(childView.getLayoutParams().width, childView.getLayoutParams().height);
    
            // 测试下:子控件(孩子类)的高和宽
            /**
             * 注意:为什么在这里又可以获取到子控件的宽和高呢?
             *      因为子控件是View 并且这个View在测量方法中执行了 setMeasuredDimension(w, h);
             */
            Log.d(TAG, "childView.getMeasuredWidth():" + childView.getMeasuredWidth() +
                    " childView.getMeasuredHeight():" + childView.getMeasuredHeight());
        }
    
        /**
         * 排版 指定位置,只给子控件指定位置的
         * @param changed 当发生改变
         * @param l 左边线距离左边距离
         * @param t 上边线距离上边距离
         * @param r 右边线距离左边距离
         * @param b 底边线距离上边距离
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            // 测试下:获取自身当前FatherViewGroup测量后的宽和高
            // 注意:在这里无法获取,还不知道是什么原因!!!,
            // 爷爷类可以获取到是因为爷爷类的父类控件是系统的RelativeLayout
            int measuredWidth = getMeasuredWidth();
            int measuredHeight = getMeasuredHeight();
            Log.d(TAG, "爸爸类 >>> measuredWidth:" + measuredWidth + " measuredHeight:" + measuredHeight);
    
            // 给子控件(爸爸类)指定位置
            // Y轴与X轴移动计算一样,X计算:移动X轴方向,得到自身(FatherViewGroup爸爸类)宽度的一半 减 子控件(孩子类)的一本就居中了
            int childL = (w / 2) - (childView.getMeasuredWidth() / 2);
            int childT = (h / 2) - (childView.getMeasuredHeight() / 2);
    
            // L位置增加了,R位置也需要增加
            int childR = childView.getMeasuredWidth() + childL;
            int childB = childView.getMeasuredHeight() + childT;
    
            childView.layout(childL, childT, childR, childB);
        }
    
        /**
         * 为什么继承了ViewGroup就不需要onDraw绘制了,因为绘制都是在View中处理
         * 继承了ViewGroup需要做好测量-->排版固定位置就好了,绘制是交给View去处理
         */
    }

    孩子类,ChildView:

    package custom.view.upgrade.view_viewgroup_theory;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.view.View;
    
    /**
     * 孩子类,孩子类暂时还没有自己的孩子,所有是继承View
     */
    public class ChildView extends View {
    
        private static final String CONTEXT = "child";
    
        // 创建画笔把文字画到画布中去
        private Paint mPaint;
    
        private Rect rect;
    
        /**
         * 此两个参数的构造方法,用于在xml布局指定初始化,并传入xml中的所有属性
         * @param context
         * @param attrs
         */
        public ChildView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
    
            mPaint =  new Paint();
            // 画笔防锯齿
            mPaint.setAntiAlias(true);
            // 画笔白色
            mPaint.setColor(Color.WHITE);
            mPaint.setTextSize(40);
    
            rect = new Rect();
        }
    
        /**
         * 此高宽是父控件(爸爸类)传递过来的高和宽
         */
        private int w;
        private int h;
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            w = MeasureSpec.getSize(widthMeasureSpec);
            h = MeasureSpec.getSize(heightMeasureSpec);
            setMeasuredDimension(w, h);
        }
    
        /**
         * 绘制的方法
         * @param canvas 画布
         */
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            // 拿到自身宽度的一半 减 文字宽度的一半
    
            mPaint.getTextBounds(CONTEXT, 0, CONTEXT.length(), rect);
    
            int x = (w / 2) - (rect.width() / 2);
            int y = (h / 2) - (rect.height() / 2) + rect.height();
            canvas.drawText(CONTEXT, x, y , mPaint);
        }
    }

    效果图:

  • 相关阅读:
    JVM 关于对象分配在堆、栈、TLAB的理解
    分布式唯一 ID 生成方案有哪些?
    JVM 栈帧之操作数栈与局部变量表 转
    C# TreeHelper帮助类
    Java:Top K问题的解法
    C#单例模式
    C#分组方式比较
    Vue实现登录
    git使用总结
    js实现无色彩球
  • 原文地址:https://www.cnblogs.com/android-deli/p/9716805.html
Copyright © 2011-2022 走看看