自定义View基础与原理

来源:互联网 发布:淘宝卖家20万限额 编辑:程序博客网 时间:2024/06/16 12:26

这里写链接内容# 自定义View基础与原理


什么是自定义View

其实就是继承系统的View,然后加入绘制元素(文字/图形)和逻辑,最终达到自己想要想过的控件。


为什么使用自定义View

  • 特定的显示风格
  • 处理特有的用户交互
  • 优化布局
  • 封装等

如何自定义控件

编写自己的自定义View

- 编写最简单的自定义View,什么都不显示,但是有View的特性
/**     * java代码创建视图的时候被调用,如果是从xml填充的视图,就不会调用这个     *      * @param context     */    public MyView(Context context) {        super(context);    }    /**     * 这个是在xml创建但是没有指定style的时候被调用     *      * @param context     * @param attrs     */    public MyView(Context context, AttributeSet attrs) {        super(context, attrs);    }    /**     * 这个是在xml创建且指定style的时候被调用     *      * @param context     * @param attrs     * @param defStyleAttr     */    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }

那种情况下调用那个构造方法,上面注释已经写的很清楚了。不妨我们想一下,让第一个构造方法去调用第二个构造方法,第二个去调用第三个,这样会不会省去一些事呢。如下面例子的代码所示。

- 可以显示自己的元素(文字/几何图形/图片)
public class MyView extends View {    private Paint paint;    private Bitmap bitmap;    /**     * java代码创建视图的时候被调用,如果是从xml填充的视图,就不会调用这个     *      * @param context     */    public MyView(Context context) {        this(context, null);    }    /**     * 这个是在xml创建但是没有指定style的时候被调用     *      * @param context     * @param attrs     */    public MyView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    /**     * 这个是在xml创建且指定style的时候被调用     *      * @param context     * @param attrs     * @param defStyleAttr     */    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    private void init() {        // Paint样式和颜色信息,关于文字/几何图形和bitmap的        paint = new Paint();        bitmap = BitmapFactory.decodeResource(getResources(),                R.drawable.ic_launcher);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        paint.setTextSize(30);        paint.setColor(Color.BLUE);        paint.setStyle(Paint.Style.STROKE);        // 绘制文字        canvas.drawText("this is onDraw", 0, 30, paint);        // 绘制直线        canvas.drawLine(0, 60, 100, 60, paint);        // 绘制矩形        canvas.drawRect(0, 90, 100, 190, paint);        // Rect中参数int类型        Rect r = new Rect(100, 90, 200, 190);        canvas.drawRect(r, paint);        // Rectf中参数float类型        RectF rect = new RectF(200, 90, 300, 190);        canvas.drawRect(rect, paint);        // 圆角矩形rx, ry x和y方向上的弧度        RectF rect2 = new RectF(300, 90, 400, 190);        canvas.drawRoundRect(rect2, 30, 30, paint);        // 绘制圆形        canvas.drawCircle(50, 270, 50, paint);        // 绘制图片        canvas.drawBitmap(bitmap, 0, 350, paint);    }}

布局中使用

 <!-- 完整的包名和类名 -->    <com.example.myview.MyView         android:layout_width="match_parent"        android:layout_height="match_parent"/>

运行结果如图:
运行结果

加入逻辑线程

  • 怎么让元素动起来呢,需要什么
    不断的改变元素绘制的坐标位置,我们在视觉上就会感觉到元素在运动
  • 元素动起来的逻辑放在哪里
    创建一个线程,执行run方法里面改变其坐标,在进行重新绘制,android提供了在线程中重新绘制的方法postInvalidate()
  • 怎样看起来动起来的元素流畅
    一秒内绘制20次即可
public class LogicView extends View {    private Paint paint;    private float rx;    /**     * 逻辑线程     */    private MyThread mThread;    private RectF rectF;    /**     * 区间角度     */    private float sweepAngle;    public LogicView(Context context) {        this(context, null);    }    public LogicView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public LogicView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    /**     * 对类成员进行初始化     */    private void init() {        paint = new Paint();        rectF = new RectF(0, 60, 100, 160);        mThread = new MyThread();        mThread.start();    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        paint.setTextSize(30);        canvas.drawText("LogicView", rx, 30, paint);        // 起始角度/区间角度        canvas.drawArc(rectF, 0, sweepAngle, true, paint);    }    class MyThread extends Thread {        Random random = new Random();        @Override        public void run() {            super.run();            // 不断的进行绘制            while (true) {                long start = System.currentTimeMillis();                // 超出屏幕长度                if ((rx += 3) > getWidth()) {                    rx = 0 - paint.measureText("LogicView");                }                // 超出360度,清零                if (sweepAngle++ > 360) {                    sweepAngle = 0;                }                int r = random.nextInt(256);                int g = random.nextInt(256);                int b = random.nextInt(256);                paint.setARGB(255, r, g, b);                // 线程中更新绘制的方法                // postInvalidate();                invalidateView();                long end = System.currentTimeMillis();                // 一秒绘制20次即可                if (end - start < 50) {                    try {                        Thread.sleep(50 - (end - start));                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }    }    /**     * 重绘     */    private void invalidateView() {        if (Looper.getMainLooper() == Looper.myLooper()) {            invalidate();        } else {            postInvalidate();        }    }}

提取和封装自定义View

  • 封装后的基类
public abstract class BaseView extends View {    /**     * 逻辑线程     */    private MyThread mThread;    /**     * 线程的控制开关     */    private boolean isRunning = true;    public BaseView(Context context) {        this(context, null);    }    public BaseView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public BaseView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    // 这里使用final关键字,不让子类重写    @Override    protected final void onDraw(Canvas canvas) {        if (mThread == null) {            mThread = new MyThread();            mThread.start();        } else {            drawSub(canvas);        }    }    /**     * 绘制元素     *      * @param canvas     */    protected abstract void drawSub(Canvas canvas);    /**     * 逻辑处理     */    protected abstract void logic();    //离开屏幕的时候结束掉线程    @Override    protected void onDetachedFromWindow() {        super.onDetachedFromWindow();        isRunning = false;    }    class MyThread extends Thread {        @Override        public void run() {            super.run();            // 不断的进行绘制            while (isRunning) {                long start = System.currentTimeMillis();                logic();                invalidateView();                long end = System.currentTimeMillis();                // 一秒绘制20次即可                if (end - start < 50) {                    try {                        Thread.sleep(50 - (end - start));                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }    }    /**     * 重绘     */    public void invalidateView() {        if (Looper.getMainLooper() == Looper.myLooper()) {            invalidate();        } else {            postInvalidate();        }    }}

该过程中我们提出的绘制元素drawSub(Canvas canvas)方法和逻辑处理logic()方法。并将onDraw使用final关键字,不让子类去重写。当绘制离开屏幕的时候,结束掉逻辑处理线程。

使用基类

public class LogicView extends BaseView {    private Paint paint;    private float rx;    private RectF rectF;    /**     * 区间角度     */    private float sweepAngle;    private Random random;    public LogicView(Context context) {        this(context, null);    }    public LogicView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public LogicView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    /**     * 对类成员进行初始化     */    protected void init() {        paint = new Paint();        rectF = new RectF(0, 60, 100, 160);        random = new Random();    }    @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() {        // 超出屏幕长度        if ((rx += 3) > getWidth()) {            rx = 0 - paint.measureText("LogicView");        }        // 超出360度,清零        if (sweepAngle++ > 360) {            sweepAngle = 0;        }        int r = random.nextInt(256);        int g = random.nextInt(256);        int b = random.nextInt(256);        paint.setARGB(255, r, g, b);    }}

运行结果如图:
这里写图片描述

定义XML中定义的样式来影响显示效果

  • 自定义样式attr.xml
    新建自定义样式attr.xml在res/values目录下
<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="NumText">        <!-- 绘制的行数 -->        <attr name="lineNum" format="integer"></attr>        <!-- 文字是否滚动显示 -->        <attr name="isScrool" format="boolean"></attr>    </declare-styleable></resources>
  • 在布局中使用
    在使用自定义属性的时候先声明xml的命名空间
    • eclipse:
      xmlns:myview=”http://schemas.android.com/apk/res/完整的包名”
    • android studio:
      xmlns:myview=”http://schemas.android.com/apk/res-auto”
      布局中的使用:
    <com.example.myview.v4.NumText        android:layout_width="match_parent"        android:layout_height="match_parent"        myview:isScrool="true"        myview:lineNum="3" >    </com.example.myview.v4.NumText>
  • 例子代码
public class NumText extends BaseView {    /**     * 画笔     */    private Paint paint;    /**     * 绘制文字x坐标     */    private float rx;    /**     * 绘制的行数     */    private int lintNum;    private Random random;    /**     * 是否滚动     */    private boolean isScrool;    public NumText(Context context) {        this(context, null);    }    public NumText(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public NumText(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        paint = new Paint();        random = new Random();        //默认不滚动        isScrool = false;        //在view构造方法中获取自定义属性        TypedArray a = context.obtainStyledAttributes(attrs,                R.styleable.NumText);        int n = a.getIndexCount();        for (int i = 0; i < n; i++) {            int attr = a.getIndex(i);            switch (attr) {            case R.styleable.NumText_lineNum:                lintNum = (int) a.getInt(attr, 1);                break;            case R.styleable.NumText_isScrool:                isScrool = (boolean) a.getBoolean(attr, false);                break;            }        }        a.recycle();    }    @Override    protected void drawSub(Canvas canvas) {        // 绘制lineNum行文字        for (int i = 0; i < lintNum; i++) {            int textSize = 30 + i;            paint.setTextSize(textSize);            canvas.drawText("自定义View基础与原理", rx, textSize + textSize * i, paint);        }    }    @Override    protected void logic() {        if(isScrool){            // 超出屏幕长度            if ((rx += 5) > getWidth()) {                rx = 0 - paint.measureText("自定义View基础与原理");            }            // 改变画笔的颜色            int r = random.nextInt(256);            int g = random.nextInt(256);            int b = random.nextInt(256);            paint.setARGB(255, r, g, b);        }    }}

运行结果:这里写图片描述

源码下载

0 0
原创粉丝点击