美文网首页Android之界面Android开发Android知识
自定义View简单案例-自绘控件

自定义View简单案例-自绘控件

作者: Reathin | 来源:发表于2017-06-04 13:15 被阅读208次

    实现的效果

    继承View

    继承ViewGroup实现一个顶栏

    下面来实现上面效果
    自定义View基础Android开发之自定义View基础

    继承View

    需要自定义绘制内容,需要继承View,必须要重写onDraw方法,在onDraw方法中来进行绘制,实现onMeasure方法,来测量控件的空间。

    创建类,继承View或View的子类,并提供相关的构造方法

    public class SimpleView extends View {
    
        public SimpleView(Context context) {
            super(context);
        }
    
        public SimpleView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    }
    

    重写onMeasure()方法,并调用setMeasuredDimension(int width, int height)设置控件的大小(因为Demo只是简单的画了几个图形,所以没重写)

    重写onDraw()方法,实现绘制特定内容

    @Override
    protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    }
    

    重写onTouchEvent()方式处理触摸事件

    此处不作处理

    @Override
    public boolean onTouchEvent(MotionEvent event) {
            return super.onTouchEvent(event);
    }
    

    开始绘制

    1.说到绘制,当然需要画笔了。初始化画笔

        /**
         * 初始化画笔
         */
        private void initPaint() {
            // 定义画笔
            paint = new Paint();
            // 设置画笔的字体大小
            paint.setTextSize(100);
            // 线条的样式 - 粗体 斜线
            paint.setTypeface(Typeface.DEFAULT_BOLD);
            paint.setStrokeWidth(10);
            // 设置画笔的颜色
            paint.setColor(Color.RED);
            // 设置画笔的样式
            // Paint.Style.STROKE - 边线样式 - 圆(空心圆)
            // Paint.Style.FILL - 填充样式 - 圆(实心圆)
            paint.setStyle(Paint.Style.STROKE);
            //paint.setStyle(Paint.Style.FILL);
            // 是否抗锯齿
            paint.setAntiAlias(true);
        }
    

    2.绘制图形

        /**
         * 用于绘制
         * 当初始化View(在代码中添加自定义控件,或者在布局中添加加载时),此方法会调用
         *
         * @param canvas - 画布
         */
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            /****************绘制内容***************/
            // 绘制线(float startX, float startY, float stopX, float stopY, Paint paint)
            canvas.drawLine(100, 100, 600, 300, paint);
            // 绘制圆(float cx, float cy, float radius, Paint paint)
            canvas.drawCircle(500, 500, 200, paint);
            // 绘制文字(String text, float x, float y, Paint paint)
            canvas.drawText("绘制内容", 450, 750, paint);
            // 绘制矩形1(float left, float top, float right, float bottom, Paint paint)
            canvas.drawRect(400, 950, 600, 1100, paint);
            
            // 绘制矩形2
            RectF rect = new RectF();
            rect.left = 400;
            rect.top = 950;
            rect.right = 600;
            rect.bottom = 1100;
            //canvas.drawRect(rect,paint);
        }
    

    在布局文件中使用<类全名>并设置属性(或在Java代码中使用)

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 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"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.rair.customview.SimpleViewActivity">
    
        <com.rair.customview.view.SimpleView
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </android.support.constraint.ConstraintLayout>
    

    运行

    完整代码

    public class SimpleView extends View {
    
        // 声明画笔
        private Paint paint;
    
        // 创建构造方法
        public SimpleView(Context context) {
            //super(context);
            // 调用当前类的其他的构造方法
            this(context, null);
        }
    
        /**
         * 如果在布局中添加自定义控件,则需要添加如下构造方法
         *
         * @param context      - 上下文
         * @param AttributeSet set - 属性对象,它包含这当前控件所有的属性
         */
        public SimpleView(Context context, AttributeSet set) {
            super(context, set);
            initPaint();
        }
    
        /**
         * 初始化画笔
         */
        private void initPaint() {
            // 定义画笔
            paint = new Paint();
            // 设置画笔的字体大小
            paint.setTextSize(100);
            // 线条的样式 - 粗体 斜线
            paint.setTypeface(Typeface.DEFAULT_BOLD);
            paint.setStrokeWidth(10);
            // 设置画笔的颜色
            paint.setColor(Color.RED);
            // 设置画笔的样式
            // Paint.Style.STROKE - 边线样式 - 圆(空心圆)
            // Paint.Style.FILL - 填充样式 - 圆(实心圆)
            paint.setStyle(Paint.Style.STROKE);
            //paint.setStyle(Paint.Style.FILL);
            // 是否抗锯齿
            paint.setAntiAlias(true);
        }
    
        /**
         * 用于绘制
         * 当初始化View(在代码中添加自定义控件,或者在布局中添加加载时),此方法会调用
         *
         * @param canvas - 画布
         */
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            /****************绘制内容***************/
            // 绘制线
            canvas.drawLine(100, 100, 600, 300, paint);
            // 绘制圆
            canvas.drawCircle(500, 500, 200, paint);
            // 绘制文字
            canvas.drawText("绘制内容", 450, 750, paint);
            // 绘制矩形
            RectF rect = new RectF();
            rect.left = 400;
            rect.top = 950;
            rect.right = 600;
            rect.bottom = 1100;
            //canvas.drawRect(rect,paint);
            canvas.drawRect(400, 950, 600, 1100, paint);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return super.onTouchEvent(event);
        }
    }
    

    继承ViewGroup

    • 绘制,需要绘制控件,重写onDraw方法
    • 通过在ViewGroup中通过this.addView(<控件对象>)来添加控件
    • onMeasure方法,来测量控件的空间
    • onLayout方法必须实现,在此方法中处理子控件的位置。当视图初始化,或者视图位置发生改变时候,调用此方法。

    创建类,继承ViewGroup

    public class GroupView extends ViewGroup {
        public GroupView(Context context) {
            this(context, null);
        }
    
        public GroupView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    }
    

    继承的ViewGroup,通过addView添加子View

    效果中左为Imageiew,中间是TextView

        private ImageView imageView;
        private TextView tvTitle;
    
        /**
         * 添加子控件
         */
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        private void addViews(Context context) {
            imageView = new ImageView(context);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageView.setBackground(backDrawable);
            LayoutParams layoutParam = new LayoutParams(100, 100);
            imageView.setLayoutParams(layoutParam);
    
            // 添加一个ImageView
            this.addView(imageView);
            // 添加一个TextView
            tvTitle = new TextView(context);
            tvTitle.setTextSize(20);
            tvTitle.setText(title);
            tvTitle.setTextColor(titleColor);
            this.addView(tvTitle);
        }
    

    重写onMeasure()方法,并调用setMeasuredDimension(int width, int height)设置控件的大小

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            // 要测量子控件的高度和宽度
            // 计算子控件
            measureChildren(widthMeasureSpec, heightMeasureSpec);
            // 先计量控件的宽度
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int measureWidth = 0;
            // 确切大小 500dp 或者 match_parent
            if (widthMode == MeasureSpec.EXACTLY) {
                measureWidth = MeasureSpec.getSize(widthMeasureSpec);
            }
            // wrap_content
            else if (widthMode == MeasureSpec.AT_MOST) {
                // 设置屏幕的宽度
                measureWidth = screenWidth;
            }
            // 高度计算,获取最高的控件
            int measureHeight = 0;
            int childCount = this.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = this.getChildAt(i);
                // child.getMeasuredHeight()能够获取到具体的控件大小
                // 必须先调用measureChildren(widthMeasureSpec,heightMeasureSpec);
                // 判断哪个控件的高度最大,来作为视图容器的高度
                if (child.getMeasuredHeight() > measureHeight) {
                    measureHeight = child.getMeasuredHeight();
                }
            }
            setMeasuredDimension(measureWidth, measureHeight + 50);
        }
    
    • int MeasureSpec.getMode(mSpec) 获取控件大小模式
      • MeasureSpec.EXACTLY,确切空间,布局中的属性值一般为match_parent或确切固定的值(如指定了160dp)
      • MeasureSpec.AT_MOST 尽量多的空间,布局中的属性值wrap_content
      • MeasureSpec.UNSPECIFIED 未指定的,一般在父控件中使用
    • MeasureSpec.getSize(mSpec) 获取控件大小

    添加自定义View属性(使控件更灵活)

    <resources>
        <declare-styleable name="GroupView">
            <!-- 背景 -->
            <attr name="nav_bg" format="color|reference" />
            <!-- 标题颜色 -->
            <attr name="title_color" format="color|reference" />
            <!-- 标题文字 -->
            <attr name="title_text" format="string|reference" />
            <!-- 标题文字大小 -->
            <attr name="title_size" format="dimension|reference" />
            <!-- 返回图标 -->
            <attr name="back_image" format="reference" />
        </declare-styleable>
    </resources>
    

    解析自定义属性

            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GroupView);
            title = array.getString(R.styleable.GroupView_title_text);
            bgColor = array.getColor(R.styleable.GroupView_nav_bg, 0x0);
            titleColor = array.getColor(R.styleable.GroupView_title_color, 0xF);
            backDrawable = array.getDrawable(R.styleable.GroupView_back_image);
            titleSize = array.getDimensionPixelSize(R.styleable.GroupView_title_size, 10);
            array.recycle();
            // 设置背景颜色
            this.setBackgroundColor(bgColor);
            // 初始化画笔
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setStyle(Paint.Style.FILL);
            paint.setColor(bgColor);
    

    实现onLayout方法(俗称定位,就是确定每个子View的位置)

    onLayout方法必须实现,在此方法中处理子控件的位置。当视图初始化,或者视图位置发生改变时候,调用此方法。

    具体按需求计算

        /**
         * 当视图初始化,或者视图位置发生改变时候
         *
         * @param changed 是否改变
         * @param l 左
         * @param t 上
         * @param r 右
         * @param b 下
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            //getWidth()
            // 需要设置子控件显示的位置
            //getChildAt(0).layout();
            // 控件的宽度
            int viewWidth = getMeasuredWidth();
            int viewHeight = getMeasuredHeight();
            int left = 0, top = 0, right = 0, bottom = 0;
            int childCount = this.getChildCount();
            View view = null;
            for (int i = 0; i < childCount; i++) {
                view = this.getChildAt(i);
                if (i == 0) {
                    left = view.getMeasuredWidth() / 3;
                } else if (i == 1) {
                    left = (viewWidth - view.getMeasuredWidth()) / 2;
                }
                top = (viewHeight - view.getMeasuredHeight()) / 2;
                right = left + view.getMeasuredWidth();
                bottom = top + view.getMeasuredHeight();
                view.layout(left, top, right, bottom);
            }
        }
    

    添加回调接口(返回按钮点击事件)

        public interface OnFinishListener {
            void onFinish();
        }
    
        private OnFinishListener listener;
    
        public void setOnFinishListener(OnFinishListener listener) {
            this.listener = listener;
        }
    

    在布局文件中使用<类全名>并设置属性(或在Java代码中使用)

    <?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"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.rair.customview.GroupViewActivity">
    
        <com.rair.customview.view.GroupView
            android:id="@+id/m_groupview"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:back_image="@drawable/ic_arrow_back_black_24dp"
            app:nav_bg="@color/colorAccent"
            app:title_color="@color/white"
            app:title_size="25sp"
            app:title_text="首页" />
    
        <com.rair.customview.view.GroupView
            android:id="@+id/m_groupview1"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:layout_marginTop="10dp"
            app:back_image="@drawable/ic_arrow_back_black_24dp"
            app:nav_bg="@color/colorAccent"
            app:title_color="@color/white"
            app:title_size="25sp"
            app:title_text="首页" />
    </LinearLayout>
    

    设置事件监听(点击返回退出Activity)

    public class GroupViewActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_group_view);
    
            GroupView groupView = (GroupView) findViewById(R.id.m_groupview);
            groupView.setOnFinishListener(new GroupView.OnFinishListener() {
                @Override
                public void onFinish() {
                    finish();
                }
            });
        }
    }
    

    运行

    Demo已上传

    GitHub:https://github.com/Rairmmd/RairDemo/tree/master/CustomViewDemo
    Coding:https://git.coding.net/Rair/RairDemo.git

    相关文章

      网友评论

        本文标题:自定义View简单案例-自绘控件

        本文链接:https://www.haomeiwen.com/subject/bcvvfxtx.html