自己定义 View 基础和原理

课程背景:

在 Android 提供的系统控件不能满足需求的情况下,往往须要自己开发自己定义 View 来满足需求,可是该怎样下手呢。本课程将带你进入自己定义 View 的开发过程,来了解它的一些原理。

核心内容:

1.编写自己的自己定义 View

2.增加逻辑线程

3.提取和封装自己定义 View

4.利用 xml 中定义样式来影响显示效果

编写自己的自己定义 View(上)

本课时主要解说最简单的自己定义 View,然后增加绘制元素(文字、图形等),而且能够像使用系统控件一样在布局中使用。

本课时将要做两件事情:

编写最简单的自己定义View,什么都不显示。可是有View的特性

能够显示自己的元素

本课时的知识要点包含:

最简单的自己定义View。继承View

通过覆盖View的onDraw方法来实现自主显示

利用Canvas和paint来绘制显示元素(文字。几何图形等)


public class MyView extends View{

    private Bitmap bitmap;

    //须要增加两个构造方法(第一个在代码中使用,第二个在布局中使用)
    public MyView(Context context) {
        super(context);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
    }

    //通过View提供的onDraw方法增加绘制元素
    @Override
    protected void onDraw(Canvas canvas) {//Canvas能够绘制文字、集合图形、Bitmap(图片)

        //The Paint class holds the style and color information about how to draw geometries, text and bitmaps.
        Paint paint = new Paint();
        paint.setTextSize(30);//设置文字大小
        paint.setColor(0xffff0000);//通过16进制的方式设置paint的颜色,以16进制代表的颜色意义是:a,r,g,b
        /**
         * 绘制文字
         * 參数
         * text,x,y,paint
         */
        canvas.drawText("this is onDraw", 0, 30, paint);

        //绘制几何元素

        /**
         * 绘制直线
         * 參数
         * startX, startY, stopX, stopY, paint
         */
        canvas.drawLine(0, 60, 100, 60, paint);

        paint.setStyle(Style.STROKE);//设置图型样式为空心

        /**
         * 绘制矩形
         * 參数
         * left, top, right, bottom, paint
         */
        //canvas.drawRect(0, 90, 100, 190, paint);

        //另外两个绘制矩形方法
        //Rect r = new Rect(0, 90, 100, 190);
        //canvas.drawRect(r, paint);

        RectF r = new RectF(0, 90, 100, 190);
        //canvas.drawRect(r, paint);

        /**
         * 绘制圆角的矩形
         * 參数
         * rect, rx, ry, paint
         * rx, ry:代表X轴和Y轴的弧度
         */
        //canvas.drawRoundRect(r, 10, 10, paint);

        /**
         * 绘制圆形
         * 參数:cx, cy, radius, paint
         * cx, cy:圆心位置。radius半径
         */
        canvas.drawCircle(50, 270, 50, paint);

        /**
         * 绘制图片
         * 參数:bitmap, left, top, paint
         * left, top:左上坐标
         */
        canvas.drawBitmap(bitmap, 0, 350, paint);

    }
}

上面通过Paint对象能够影响绘制元素的颜色和样式信息

    <!-- 在布局中增加自己定义View,该自己定义View继承了系统的View所以他有系统View的一些属性 -->

    <com.example.myview.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#00ff00" />

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // setContentView(R.layout.activity_main);

        // 在代码中实例化自己定义view(通过代码的方式增加自己定义view)
        setContentView(new MyView(this));
    }

}

增加逻辑线程

本课时须要让绘制的元素动起来,可是又不堵塞主线程。所以引入逻辑线程。

在子线程更新 UI 是不被同意的。可是 View 提供了方法。让我们来看看吧。

本课时的背景:

怎么让元素动起来。须要什么

让元素动起来的逻辑放在哪里

逻辑怎么影响绘制

怎么让元素看起来流畅

本课时的知识要点包含:

让文字动起来,改变坐标

在线程中改动坐标(增加循环,时间睡眠)

又一次绘制元素(两种方式)

线程休眠时间控制(去除逻辑处理时间)

案例效果:

public class LogicView extends View {

    private Paint paint = new Paint();

    private float rx = 0;
    private MyThread thread;
    private RectF rectF = new RectF(0, 60, 100, 160);
    private float sweepAngle = 0;

    public LogicView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public LogicView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onDraw(Canvas canvas) {

        paint.setTextSize(30);
        canvas.drawText("LogicView", rx, 30, paint);

        canvas.drawArc(rectF, 0, sweepAngle, true, paint);

        if (thread == null) {
            thread = new MyThread();
            thread.start();
        }

    }

    class MyThread extends Thread {

        Random rand = new Random();

        @Override
        public void run() {

            while (true) {
                rx += 3;

                if (rx > getWidth()) {// 超出屏幕是回到屏幕
                    rx = 0 - paint.measureText("LogicView");// measureText方法測量文字的宽度
                }

                // ----------------------------
                sweepAngle++;
                if (sweepAngle > 360) {
                    sweepAngle = 0;
                }

                int r = rand.nextInt(256);// 0-255
                int g = rand.nextInt(256);// 0-255
                int b = rand.nextInt(256);// 0-255
                paint.setARGB(255, r, g, b);// 第一个255全然不透明

                // 又一次绘制,调用View提供的在线程中更新绘制的方法onDraw
                postInvalidate();// 出来该方法还能够Handler的方式自主线程中调用Invalidate()方法

                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

回头再查看LogicView发现他的代码还是比較复杂的下一课时将要抽取和封装自己定义View。简化代码和逻辑(用最简单的方式实现最炫的效果

提取和封装自己定义View

本课时主要解说在上个课程的基础上,进行提代替码来构造自己定义 View 的基类。主要目的是:创建新的自己定义 View 时。仅仅需继承此类并仅仅关心绘制和逻辑。其它工作由父类完毕。这样既降低反复编码,也简化了逻辑。

本课时的背景:

为什么封装自己定义View,全然能够ctrl+c和ctrl+v啊

怎样做到简化代码和逻辑

怎样禁止子类改动操作(父类的某些方法不希望子类来改动

演示效果和对照代码逻辑的差异

本课时解说的内容包含:

封装自己定义基类,抽象绘制和逻辑方法

将onDraw变为final方法

利用封装的自己定义View的基类来演示原效果

本课时是基于上一课时的代码进行提取和封装。下面的BaseView就是封装的父类(提取和封装主要从两个方向进行,绘制方面和逻辑方面)为了具体介绍这里分为下面两步:

代码的提取:

public class BaaseView extends View {

    private Paint paint = new Paint();

    private float rx = 0;
    private MyThread thread;
    private RectF rectF = new RectF(0, 60, 100, 160);
    private float sweepAngle = 0;
    Random rand = new Random();

    public BaaseView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public BaaseView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    // 提取绘制方面的代码
    private void drawSub(Canvas canvas) {
        paint.setTextSize(30);
        canvas.drawText("LogicView", rx, 30, paint);
        canvas.drawArc(rectF, 0, sweepAngle, true, paint);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        if (thread == null) {
            thread = new MyThread();
            thread.start();
        } else {
            drawSub(canvas);
        }

    }

    // 提取逻辑代码
    private void logic() {
        rx += 3;

        if (rx > getWidth()) {// 超出屏幕是回到屏幕
            rx = 0 - paint.measureText("LogicView");// measureText方法測量文字的宽度
        }

        // ----------------------------
        sweepAngle++;
        if (sweepAngle > 360) {
            sweepAngle = 0;
        }

        int r = rand.nextInt(256);// 0-255
        int g = rand.nextInt(256);// 0-255
        int b = rand.nextInt(256);// 0-255
        paint.setARGB(255, r, g, b);// 第一个255全然不透明
    }

    class MyThread extends Thread {

        @Override
        public void run() {

            while (true) {

                logic();

                // 又一次绘制。调用View提供的在线程中更新绘制的方法onDraw
                postInvalidate();// 出来该方法还能够Handler的方式自主线程中调用Invalidate()方法

                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

代码的封装:(怎样进行封装呢?这里就须要对父类进行一些处理,如绘制方面的内容希望在子类中实现父类仅仅进行管理。也就是说drawSub方法不在父类中绘制,这就须要把他变成抽象方法在子类中必须实现它,逻辑方面也相同希望通过子类来实现具体的处理父类中也仅仅进行管理,由于这些方法须要子类中使用所以须要把他的修饰符改为受保护的protected。又由于有了抽象方法说以类也要改为抽象类)

public abstract class BaseView extends View {

    private MyThread thread;

    public BaseView(Context context) {
        super(context);
    }

    public BaseView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    protected abstract void drawSub(Canvas canvas);

    protected abstract void logic();

    @Override
    protected void onDraw(Canvas canvas) {

        if (thread == null) {
            thread = new MyThread();
            thread.start();
        } else {
            drawSub(canvas);
        }

    }

    class MyThread extends Thread {

        @Override
        public void run() {

            while (true) {

                logic();

                postInvalidate();

                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

到这里一个封装的BaaseView 就完毕了。如今之须要一个子类来进行演示

public class LogicView extends BaseView {

    private Paint paint = new Paint();

    private float rx = 0;
    private RectF rectF = new RectF(0, 60, 100, 160);
    private float sweepAngle = 0;
    Random rand = new Random();

    public LogicView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public LogicView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void drawSub(Canvas canvas) {
        paint.setTextSize(30);
        canvas.drawText("LogicView", rx, 30, paint);
        canvas.drawArc(rectF, 0, sweepAngle, true, paint);
    }

    @Override
    protected void logic() {
        rx += 3;

        if (rx > getWidth()) {// 超出屏幕是回到屏幕
            rx = 0 - paint.measureText("LogicView");// measureText方法測量文字的宽度
        }

        // ----------------------------
        sweepAngle++;
        if (sweepAngle > 360) {
            sweepAngle = 0;
        }

        int r = rand.nextInt(256);// 0-255
        int g = rand.nextInt(256);// 0-255
        int b = rand.nextInt(256);// 0-255
        paint.setARGB(255, r, g, b);// 第一个255全然不透明
    }
}

以上就完毕了对第二课时(增加逻辑线程)的绘制方面和逻辑方面的代码提取和封装,将该自己定义view增加布局文件里执行后效果与第二课时一样。

可能通过上面的解说还是比較混乱。所以接下来通过继承BaseView再定义一个简单的自己定义View(MyText),效果还是在屏幕上滚动。

public class MyText extends BaseView {

    private Paint paint = new Paint();
    private float rx = 0;

    public MyText(Context context) {
        super(context);
    }

    public MyText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void drawSub(Canvas canvas) {

        paint.setTextSize(30);
        canvas.drawText("MyText", rx, 03, paint);
    }

    @Override
    protected void logic() {

        rx += 3;
        if (rx > getWidth()) {
            rx = -paint.measureText("MyText");
        }
    }
}

到这里事实上BaseView还是有缺陷的,如onDraw在子类中是能够覆盖的。可是在BaseView中onDraw是做了一些处理的,假设在子类中将覆盖的onDraw方法中super.onDraw(canvas)去掉,那子类中的drawSub方法是不执行的。所以须要在父类中禁止子类覆盖onDraw方法,此时就能够使用final修师符。在BaseView的线程中,循环是死循环,可是我们要在离开屏幕的时让循环结束掉,下面就是改动后的完整的BaseView。

public abstract class BaseView extends View {

    private MyThread thread;

    public BaseView(Context context) {
        super(context);
    }

    public BaseView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    protected abstract void drawSub(Canvas canvas);

    protected abstract void logic();

    @Override
    protected final void onDraw(Canvas canvas) {

        if (thread == null) {
            thread = new MyThread();
            thread.start();
        } else {
            drawSub(canvas);
        }

    }

    // 离开屏幕的方法
    @Override
    protected void onDetachedFromWindow() {
        running = false; // 离开屏幕使让线程中的循环结束
        super.onDetachedFromWindow();
    }

    private boolean running = true;

    class MyThread extends Thread {

        @Override
        public void run() {

            while (running) {

                logic();

                postInvalidate();

                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在 xml 中定义样式来影响显示效果

本课时仅仅有一件事要做:

我不想改动代码。可是我想要不一样的效果

本课时的知识要点包含:

在xml中定义样式和属性

在布局中使用属性(命名空间须要声明)

在代码中解析样式的属性

在代码中使用属性对显示效果产生影响

假如如今有这样一个需求:想绘制相同的文字。可是要绘制不同的行数和是否在屏幕上滚动。(依据上面的知识要点里实现

在xml中定义样式和属性 (res——>value——>attr.xml)

<?xml version="1.0" encoding="utf-8"?

>
<resources>

    <!-- 定义样式属性 -->
    <declare-styleable name="NumText">
        <attr name="lineNum" format="integer"></attr>
        <attr name="xScroll" format="boolean"></attr>
    </declare-styleable>

</resources>

在布局中使用属性(命名空间须要声明)

<?

xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:nt="http://schemas.android.com/apk/res/com.example.myview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.nf.myview.v4.NumText
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        nt:lineNum="3"
        nt:xScroll="true" />

</LinearLayout>

在代码中解析样式的属性 与 在代码中使用属性对显示效果产生影响

public class NumText extends BaseView {

    private Paint paint = new Paint();
    private int lineNum = 0;
    private float mx = 0;
    private boolean xScroll = false;

    public NumText(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public NumText(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 获取样式属性中的一个列表
        TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.NumText);
        // 解析lineNum,方法參数介绍:index样式中的属性。defValue:默认值
        lineNum = ta.getInt(R.styleable.NumText_lineNum, 1);// NumText_lineNum样式属性的标识方法

        xScroll = ta.getBoolean(R.styleable.NumText_xScroll, false);

        ta.recycle();// 释放TypedArray
    }

    @Override
    protected void drawSub(Canvas canvas) {
        // TODO Auto-generated method stub

        for (int i = 0; i < lineNum; i++) {
            int textSize = 30 + i;
            paint.setTextSize(textSize);
            canvas.drawText("NumText", mx, textSize + textSize * i, paint);
        }

    }

    @Override
    protected void logic() {

        if (xScroll) {

            mx += 3;
            if (mx > getWidth()) {
                mx = -paint.measureText("NumText");
            }
        }
    }
}

本课程中我们学习了自己定义View基础和原理。

你应当掌握了下面知识:

能够编写自己的自己定义View

知道增加逻辑线程的目的

理解抽象和封装自己定义View基类的目的

能够在xml中定义样式和属性并对显示效果进行影响

你能够使用这些技巧来编写几个自己的自己定义View。能够是包含文字或图形的,然后,通过xml样式属性的使用来影响它们的位置或其它显示效果。假设想继续提高,你能够继续在学习Android的粒子和动画效果系列文章。

时间: 2024-08-09 03:29:37

自己定义 View 基础和原理的相关文章

Android自己定义View基础篇(三)之SwitchButton开关

自己定义View基础篇(二) 自己定义View基础篇(一) 自己定义View原理 我在解说之前,先来看看效果图,有图有真相:(转换gif图片效果太差) 那来看看真实图片: 假设你要更改样式,请改动例如以下图片: switch_ball switch_bg switch_black switch_bottom 我在这里就不反复解说View与ViewGroup的关系,View的绘制流程.假设你对自己定义View还不甚了解.请看上面几篇文章. 用法 xml文件: <com.github.ws.swit

Path类的最全面具体解释 - 自己定义View应用系列

前言 自己定义View是Android开发人员必须了解的基础:而Path类的使用在自己定义View绘制中发挥着很关键的数据 网上有大量关于自己定义View中Path类的文章.但存在一些问题:内容不全.思路不清晰.简单问题复杂化等等 今天.我将全面总结自己定义View中Path类的使用,我能保证这是市面上的最全面.最清晰.最易懂的 文章较长,建议收藏等充足时间再进行阅读 阅读本文前请先阅读自己定义View基础 - 最易懂的自己定义View原理系列 文件夹 1. 简单介绍 定义:路径.即无数个点连起

Android画图系列(二)——自己定义View绘制基本图形

这个系列主要是介绍下Android自己定义View和Android画图机制.自己能力有限.假设在介绍过程中有什么错误.欢迎指正 前言 在上一篇Android画图系列(一)--自己定义View基础中我们了解自己定义View相关的基本知识.只是,这些东西依然还是理论,接下来我们就实际绘制一些东西 在本篇文章中,我们先了解下面Canvas,而且画一些主要的图形 Canvas简单介绍 Canvas我们能够称之为画布.能够在上面绘制各种东西.是安卓平台2D图形绘制的基础.非常强大. 一般来说,比較基础的东

自定义View基础 - 最易懂的自定义View原理系列(1)

前言 自定义View原理是Android开发者必须了解的基础: 在了解自定义View之前,你需要有一定的知识储备: 本文将全面解析关于自定义View中的所有知识基础. 目录 1. View的分类 视图View主要分为两类: 类别 解释 特点 单一视图 即一个View,如TextView 不包含子View 视图组 即多个View组成的ViewGroup,如LinearLayout 包含子View 2. View类简介 View类是Android中各种组件的基类,如View是ViewGroup基类

安卓自己定义View进阶-Canvas之绘制基本形状

Canvas之绘制基本形状 作者微博: @GcsSloop [本系列相关文章] 在上一篇自己定义View分类与流程中我们了解自己定义View相关的基本知识,只是,这些东西依然还是理论,并不能拿来(zhuang)用(B), 这一次我们就了解一些能(zhaung)用(B)的东西. 在本篇文章中,我们先了解Canvas的基本用法,最后用一个小演示样例来结束本次教程. 一.Canvas简单介绍 Canvas我们能够称之为画布,能够在上面绘制各种东西,是安卓平台2D图形绘制的基础,非常强大. **一般来说

手把手带你画一个 时尚仪表盘 Android 自己定义View

拿到美工效果图.咱们程序猿就得画得一模一样. 为了不被老板喷,仅仅能多练啊. 听说你认为前面几篇都so easy,那今天就带你做个相对照较复杂的. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50468674 注意:每一篇博客都是建立在之前博客的基础知识上的,假设你刚接触自己定义view.能够来说说自己定义view简单学习的方式这里看我曾经的文章.记录了我学习自己定义view的过程,并且前几篇博客或多或少犯了一些错误(反复绘制,o

Android艺术开发探索第四章——View的工作原理(上)

这章就比较好玩了,主要介绍一下View的工作原理,还有自定义View的实现方法,在Android中,View是一个很重要的角色,简单来说,View是Android中视觉的呈现,在界面上Android提供了一套完整的GUI库,里面有很多控件,但是有时候往往并不能满足于需求,所以只有自定义View了,我们会简单的说下流程,然后再去实践除了View的三大流程之外,View常见的回调方法也是必须掌握的,比如构造方法,onAttach,onVisibilityChanged,onDetach,另外对于一些

Android 自己定义View (二) 进阶

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24300125 继续自己定义View之旅.前面已经介绍过一个自己定义View的基础的样例,Android 自己定义View (一),假设你还对自己定义View不了解能够去看看.今天给大家带来一个略微复杂点的样例. 自己定义View显示一张图片,以下包括图片的文本介绍,类似相片介绍什么的,只是不重要,主要是学习自己定义View的使用方法么. 还记得上一篇讲的4个步骤么: 1.自己定

Android 它们的定义View它BounceProgressBar

转载请注明出处:http://blog.csdn.net/bbld_/article/details/41246247 [Rocko's blog] 之前几天下载了非常久没用了的桌面版酷狗来用用的时候,发现当中载入歌曲的等待进度条的效果不错(个人感觉).例如以下: 然后趁着这周末两天天气较冷,窝在宿舍放下成堆的操作系统作业(目測要抄一节多课的一堆堆文字了啊...啊..)毅然决定把它鼓捣出来,终于的效果例如以下(总感觉有点不和谐啊·): 对照能看出来的就是多了形状的选择还有使用图片了.那么接下来就