首页 > 代码库 > 自己定义 View 基础和原理

自己定义 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的粒子和动画效果系列文章。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

自己定义 View 基础和原理