美文网首页 Android知识进阶(遥远的重头开始)
Android-自定义View-onDraw方法起步

Android-自定义View-onDraw方法起步

作者: MonkeyLei | 来源:发表于2019-07-12 09:45 被阅读1次

考驾照了哟,我们就从onDraw方法开始吧。简单粗暴。或许我们不熟悉自定义View以前,还去看了Android艺术探索里面的详解,有可能看的是一脸懵逼,现在回过头来,我们一点点去实践,作为小白的我觉得这是我的方式,你或许有你的入门方式。

上一篇我们已经对View有了初步的认识,可以直接开始定义这个类了,然后把基本的结构先写了:

**1. **MyTextView01.java - 一般我会先把结构先基本写好,顺便把要做的一些todo一下

package me.heyclock.hl.customcopy;

import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/*
*@Description: 自定义绘制圆圈
*@Author: hl
*@Time: 2018/10/12 9:37
*/
public class MyTextView01 extends View {
    private Context context;///< 上下文

    public MyTextView01(Context context) {
        this(context, null);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, 0, 0);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        this.context = context;
        ///< 1\. 做一些绘制初始化
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);
        ///< 2.进行绘制
    }
}

布局文件里面添加一下控件: activity_main.xml (添加了一个背景,方便进行绘制的观察) - 200*200就是上篇提到的视图占据的矩形区域,也就是我们画布,你可以在该区域任意涂抹

 <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <me.heyclock.hl.customcopy.MyTextView01
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@color/colorPrimary" />

</android.support.constraint.ConstraintLayout>

**2. **开始进行绘制之前了,我们了解下绘制的一些个知识(不一定每个人都那么熟)

Canvas | Android Developers --- 关于画布的知识

Paint | Android Developers --- 关于画笔的知识

目的:用画笔在画布上瞎几把涂抹

画布 - 第二个构造函数眼熟不?以前有对Bitmap进行诸多操作的同学会眼熟哈...:

image

<figcaption style="margin-top: 0.66667em; padding: 0px 1em; font-size: 0.9em; line-height: 1.5; text-align: center; color: rgb(153, 153, 153);">可以基于Bitmap进行画布的创建,绘制内容将映射到Bitmap上</figcaption>

--画布绘制 - 比如画个圈圈吧:

image

<figcaption style="margin-top: 0.66667em; padding: 0px 1em; font-size: 0.9em; line-height: 1.5; text-align: center; color: rgb(153, 153, 153);">圈圈中心点,半径,以及需要的画笔,没画笔画毛呀</figcaption>

画笔 -- 基本能看到哈!至于第二个构造函数不太懂,需要研究下。这篇没专门分析这些,所以我们用第一个就行:

image

--画笔的简单设置 - 比如颜色

image

**3. **好了,我们开始进行一些初始化和简单绘制吧

MyTextView01.java

package me.heyclock.hl.customcopy;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/*
 *@Description: 自定义绘制文本
 *@Author: hl
 *@Time: 2018/10/12 9:37
 */
public class MyTextView01 extends View {
    /* 官方文档:
        https://developer.android.google.cn/reference/android/graphics/Canvas
        https://developer.android.google.cn/reference/android/graphics/Paint
   */
    private Context context;///< 上下文
    private Canvas canvas;  ///< 画布
    private Paint paint;    ///< 画笔

    public MyTextView01(Context context) {
        this(context, null);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, 0, 0);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        this.context = context;
        ///< 1\. 做一些绘制初始化
        canvas = new Canvas();  ///< 也可以指定绘制到Bitmap上面 -> Canvas(Bitmap bitmap)
        paint = new Paint();
        paint.setColor(Color.parseColor("#F50808"));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);
        ///< 2.进行绘制
        ///< 绘制一个圆圈吧-> drawCircle(float cx, float cy, float radius, Paint paint)
        canvas.drawCircle(100, 100, 50, paint);
    }
}

看效果哟 - 圆圈出来了呀呀,但是没有居中,不好看? 布局中我们控件是200dp*200dp, 然而我们的中心点是100px, 100xp, 然后半径是50px, So, 需要做一个dp转px的转换:

image

**3.1 **进一步完善下效果

package me.heyclock.hl.customcopy;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

/*
 *@Description: 自定义绘制文本
 *@Author: hl
 *@Time: 2018/10/12 9:37
 */
public class MyTextView01 extends View {
    /* 官方文档:
        https://developer.android.google.cn/reference/android/graphics/Canvas
        https://developer.android.google.cn/reference/android/graphics/Paint

    public void drawText (CharSequence text,
                          int start,
                          int end,
                          float x,
                          float y,
                          Paint paint)*/
    private Context context;///< 上下文
    private Canvas canvas;  ///< 画布
    private Paint paint;    ///< 画笔

    public MyTextView01(Context context) {
        this(context, null);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, 0, 0);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        this.context = context;
        ///< 1\. 做一些绘制初始化
        canvas = new Canvas();  ///< 也可以指定绘制到Bitmap上面 -> Canvas(Bitmap bitmap)
        paint = new Paint();
        paint.setColor(Color.parseColor("#F50808"));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);
        ///< 2.进行绘制
        ///< 绘制一个圆圈吧-> drawCircle(float cx, float cy, float radius, Paint paint)
        canvas.drawCircle(dp2px(context, 100), dp2px(context,100), dp2px(context,50), paint);
    }

    /**
     * dp转px
     * @param dp
     * @return
     */
    public static int dp2px(Context context, int dp){
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
    }
}
image

看样子还行的样子....

咿呀!这里我们就简单的定义了一个控件,这个控件特别丑,就中间一个小圈圈而已...

**4. **小白希望可以点击的时候,这个红色的圈能有呼吸灯的效果

{大概想法:点击的时候启动一个定时器,每500ms重新绘制一次,每次都增大半径10, 然后增量达到50时,就开始减10, 减量到50后开始重复循环}

4.1 第一步就是先解决点击事件的实现哈

根据前面的学习,我们可以重载

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

返回值解释:以前可能经常听说事件拦截啥的,这里如果返回true,表示消费掉该事件,且不希望其他回调方法再次处理。否则返回false。 我们不在往上级Viewgroup传递,所以返回true就行。

4. 2 重点我们看下这个MotionEvent | Android Developers, 可以先大概了解下有哪些方法可以供我们使用,用于处理点击事件逻辑。

比如下面的一些方法

image
     @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("test", "getX=" + event.getX());
        Log.e("test", "getY=" + event.getY());
        Log.e("test", "getRawX=" + event.getRawX());
        Log.e("test", "getRawY=" + event.getRawY());
        return true;
    }
image

up 当点击红色中心点时, 从上面看这个rawY明显比y大,具体可以看解释:

image image

一个getX是基于控件本身的x左边, 一个getRawX是基于屏幕的x左边, 都是左上角哈!

为了便于验证我们搞一个系统文本控件放在中间点击看看效果吧(目前我们自定义的控件还没有进行位置布局,暂时还不能通过xml设置把它挪动过去哈, I think):

   <?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=".MainActivity">

    <me.heyclock.hl.customcopy.MyTextView01
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginLeft="100dp"
        android:background="@color/colorPrimary" />

    <TextView
        android:id="@+id/testTouch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#00ddd0"
        android:text="你个扑街"
        android:textColor="#000000"
        android:textSize="50sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

代码设置Touch监听:

        TextView textView = findViewById(R.id.testTouch);
        textView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("textView", "getX=" + event.getX());
                Log.e("textView", "getY=" + event.getY());
                Log.e("textView", "getRawX=" + event.getRawX());
                Log.e("textView", "getRawY=" + event.getRawY());
                return true;
            }
        });
image

<figcaption style="margin-top: 0.66667em; padding: 0px 1em; font-size: 0.9em; line-height: 1.5; text-align: center; color: rgb(153, 153, 153);">点击中间</figcaption>

image

<figcaption style="margin-top: 0.66667em; padding: 0px 1em; font-size: 0.9em; line-height: 1.5; text-align: center; color: rgb(153, 153, 153);">点击左下角</figcaption>

是不是已经证明了get和getRaw的区别,哇咔咔...至于event.getAction()我们看下文档就行:

image image

**4.4 **所以我们可以用get,把自定义控件的点击事件实现->限制在红心区域

MyTextView01.java

    ///< 做红色点击区域限制
    private boolean bIsDownInRedRegion = false;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("test", "getX=" + event.getX());
        Log.e("test", "getY=" + event.getY());
        //Log.e("test", "getRawX=" + event.getRawX());
        //Log.e("test", "getRawY=" + event.getRawY());

        int x = (int) event.getX();
        int y = (int) event.getY();

        ///< 控件大小是: dp2px(context, 200) * dp2px(context, 200)
        ///< 圆心坐标是:  dp2px(context, 100) * dp2px(context, 100)
        ///< 圆半径是:    dp2px(context, 50)
        ///< 所以点击区域就是左上角范围(dp2px(context, 50), dp2px(context, 50))
        ///<                右下角范围:(dp2px(context, 150), dp2px(context, 150))
        int min = dp2px(context, 50);
        int max = dp2px(context, 150);
        Log.e("test", "x=" + x);
        Log.e("test", "y="+ y);
        Log.e("test", "min=" + min);
        Log.e("test", "max="+ max);
        Log.e("test", "event.getAction()=" + event.getAction());

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if (x >= min && x <= max &&
                    y >= min && y <= max){
                    bIsDownInRedRegion = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                if (bIsDownInRedRegion){
                    bIsDownInRedRegion = false;

                    if (x >= min && x <= max &&
                        y >= min && y <= max){
                        ///< 抬手时我们就可以启动定时器进行绘制刷新了
                        Log.e("test", "红色区域点击了呀,sb");
                    }
                }
                break;
        }
        return true;
    }
}

**4.5 **接下来进行绘制+刷新 - 新增updateDraw方法

  /**
     * 刷新绘制+增量变化
     */
    private static final int STEP_RADIUS = 10;  ///< 每次半径增加10
    private int changeRadius = 0;               ///< 变化量记录,达到50时则开始减;达到0就开始增加
    private boolean addFlag = true;
    private void updateDraw(){
        changeRadius = addFlag ? (changeRadius += STEP_RADIUS) : (changeRadius -= STEP_RADIUS);
        if (changeRadius > 50){
            addFlag = false;
        }else if (changeRadius < 0){
            addFlag = true;
        }
        invalidate();
    }

然后就可以进行绘制调用和刷新了

 @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);
        ///< 2.进行绘制
        ///< 绘制一个圆圈吧-> drawCircle(float cx, float cy, float radius, Paint paint)
        canvas.drawCircle(dp2px(context, 100), dp2px(context,100), dp2px(context,50 + changeRadius), paint);
    }
image

然后可以看到每次点击我们就可以看到变化:

image

**4.5.1 **接下来我们在onTouchEvent里面增加一个定时器进行刷新

 if (null == timer){
                            timer = new Timer();
                            timer.schedule(new TimerTask() {
                                @Override
                                public void run() {
                                    ///< Handler也行
                                    ((Activity)context).runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            updateDraw();
                                        }
                                    });
                                }
                            }, 0, 100);
                        }else{
                            timer.cancel();
                            timer = null;
                        }
image

Ok,到此基本绘制我们算是有所了解了呀呀呀!还是有收获,而且过程有时候也会犯一些错误,比如**private boolean bIsDownInRedRegion **= false;放到onTouchEvent内部去定义,导致怎么都不能做区域限制?(因为我们onDraw是可能会多次被调用,每次都会把变量的默认值冲掉)。

再比如关于绘制区域限制为什么要判断down,因为你点击外部,然后滑动到红色区域抬手,这个时候抬手范围可能符合要求。但是我们必须是点击的红色区域,并且抬手范围也是红色区域才能算是一次点击。

绘制我们先到这里,一些官方的文档,包括touch事件的地址也粘贴了。后面如果做复杂的自定义绘制控件,应该会经常用到。所以不要慌,慢慢来......

附上完整自定义View-基本绘制+点击事件+粗糙呼吸效果+整理下代码

MyTextView01.java

package me.heyclock.hl.customcopy;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;

import java.util.Timer;
import java.util.TimerTask;

/*
 *@Description: 自定义绘制文本
 *@Author: hl
 *@Time: 2018/10/12 9:37
 */
public class MyTextView01 extends View {
    /* 官方文档:
        https://developer.android.google.cn/reference/android/graphics/Canvas
        https://developer.android.google.cn/reference/android/graphics/Paint
    */
    private Context context;///< 上下文
    private Canvas canvas;  ///< 画布
    private Paint paint;    ///< 画笔

    ///< 做红色点击区域限制
    private boolean bIsDownInRedRegion = false;
    ///< 定时刷新
    private Timer timer = null;

    /**
     * 刷新绘制+增量变化
     */
    private static final int STEP_RADIUS = 10;  ///< 每次半径增加10
    private int changeRadius = 0;               ///< 变化量记录,达到50时则开始减;达到0就开始增加
    private boolean addFlag = true;             ///< 标记是否增加增量

    public MyTextView01(Context context) {
        this(context, null);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, 0, 0);
    }

    public MyTextView01(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        this.context = context;
        ///< 1\. 做一些绘制初始化
        canvas = new Canvas();  ///< 也可以指定绘制到Bitmap上面 -> Canvas(Bitmap bitmap)
        paint = new Paint();
        paint.setColor(Color.parseColor("#F50808"));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //super.onDraw(canvas);
        ///< 2.进行绘制
        ///< 绘制一个圆圈吧-> drawCircle(float cx, float cy, float radius, Paint paint)
        canvas.drawCircle(dp2px(context, 100), dp2px(context,100), dp2px(context,50 + changeRadius), paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("test", "getX=" + event.getX());
        Log.e("test", "getY=" + event.getY());
        //Log.e("test", "getRawX=" + event.getRawX());
        //Log.e("test", "getRawY=" + event.getRawY());

        int x = (int) event.getX();
        int y = (int) event.getY();

        ///< 控件大小是: dp2px(context, 200) * dp2px(context, 200)
        ///< 圆心坐标是:  dp2px(context, 100) * dp2px(context, 100)
        ///< 圆半径是:    dp2px(context, 50)
        ///< 所以点击区域就是左上角范围(dp2px(context, 50), dp2px(context, 50))
        ///<                右下角范围:(dp2px(context, 150), dp2px(context, 150))
        int min = dp2px(context, 50);
        int max = dp2px(context, 150);
        Log.e("test", "x=" + x);
        Log.e("test", "y="+ y);
        Log.e("test", "min=" + min);
        Log.e("test", "max="+ max);
        Log.e("test", "event.getAction()=" + event.getAction());

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if (x >= min && x <= max &&
                    y >= min && y <= max){
                    bIsDownInRedRegion = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                if (bIsDownInRedRegion){
                    bIsDownInRedRegion = false;

                    if (x >= min && x <= max &&
                        y >= min && y <= max){
                        ///< 抬手时我们就可以启动定时器进行绘制刷新了
                        Log.e("test", "红色区域点击了呀,sb");
                        if (null == timer){
                            timer = new Timer();
                            timer.schedule(new TimerTask() {
                                @Override
                                public void run() {
                                    ///< Handler也行
                                    ((Activity)context).runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            updateDraw();
                                        }
                                    });
                                }
                            }, 0, 100);
                        }else{
                            timer.cancel();
                            timer = null;
                        }
                    }
                }
                break;
        }
        return true;
    }

    /**
     * 刷新绘制+增量变化
     */
    private void updateDraw(){
        changeRadius = addFlag ? (changeRadius += STEP_RADIUS) : (changeRadius -= STEP_RADIUS);
        if (changeRadius > 50){
            addFlag = false;
        }else if (changeRadius < 0){
            addFlag = true;
        }
        invalidate();
    }

    /**
     * dp转px
     * @param dp
     * @return
     */
    public static int dp2px(Context context, int dp){
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
    }
}

相关文章

网友评论

    本文标题:Android-自定义View-onDraw方法起步

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