自定义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”
布局中的使用:
- eclipse:
<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
- 自定义View基础与原理
- Android 自定义View的原理与基础
- 自定义 View 基础和原理
- 自定义控件之View原理与使用
- 自定义控件之View原理与使用
- 自定义View基础:角度与弧度
- 自定义ScollerView,理解滑动动画与自定义view的原理
- android基础学习之自定义view的简单原理
- android自定义View一(基础和原理)
- 自定义View基础 - 最易懂的自定义View原理系列(1)
- 自定义View原理
- Android 自定义View原理
- Android自定义View基础
- 自定义View基础
- android--View自定义基础
- android自定义View基础
- 自定义 View 基础篇
- 自定义View-基础
- Vijos P1848记数问题【sprintf用法】
- LC330-google面试2016
- android 自定义控件继承TextView
- 172. Factorial Trailing Zeroes
- 获取客户端IP
- 自定义View基础与原理
- muduo库的学习---中间遇到的问题
- leetcode刷题,总结,记录,备忘, 279
- TCP的三次握手和四次挥手
- 课堂笔记1
- zzulioj 1730: 通信基站 【全排列 + DFS】
- jquery 选择器用法例(1)
- linux中man的使用
- leetcode 21. Merge Two Sorted Lists