自己定义 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的粒子和动画效果系列文章。
自己定义 View 基础和原理的更多相关文章
- Android自己定义View基础篇(三)之SwitchButton开关
自己定义View基础篇(二) 自己定义View基础篇(一) 自己定义View原理 我在解说之前,先来看看效果图,有图有真相:(转换gif图片效果太差) 那来看看真实图片: 假设你要更改样式,请改动例如 ...
- 51、自定义View基础和原理
一.编写自己的自定义View最简单的自定义View,继承View通过覆盖View的onDraw方法来实现自主显示利用Canvas和paint来绘制显示元素(文字,几何图形等) <com.myvi ...
- 自己定义View Layout过程 - 最易懂的自己定义View原理系列(3)
前言 自己定义View是Android开发人员必须了解的基础 网上有大量关于自己定义View原理的文章.但存在一些问题:内容不全.思路不清晰.无源代码分析.简单问题复杂化等等 今天,我将全面总结自己定 ...
- Android画图系列(二)——自己定义View绘制基本图形
这个系列主要是介绍下Android自己定义View和Android画图机制.自己能力有限.假设在介绍过程中有什么错误.欢迎指正 前言 在上一篇Android画图系列(一)--自己定义View基础中我们 ...
- Path类的最全面具体解释 - 自己定义View应用系列
前言 自己定义View是Android开发人员必须了解的基础:而Path类的使用在自己定义View绘制中发挥着很关键的数据 网上有大量关于自己定义View中Path类的文章.但存在一些问题:内容不全. ...
- 安卓自己定义View进阶-Canvas之绘制基本形状
Canvas之绘制基本形状 作者微博: @GcsSloop [本系列相关文章] 在上一篇自己定义View分类与流程中我们了解自己定义View相关的基本知识,只是,这些东西依然还是理论,并不能拿来(zh ...
- View基础知识
一.View基础知识 View 是Android中所有控件的基类,是一种界面层的控件的一种抽象,代表了一个控件 1.View的位置参数 View的四个属性:top(左上角纵坐标) left(左 ...
- Android艺术开发探索第四章——View的工作原理(上)
这章就比较好玩了,主要介绍一下View的工作原理,还有自定义View的实现方法,在Android中,View是一个很重要的角色,简单来说,View是Android中视觉的呈现,在界面上Android提 ...
- Android 它们的定义View它BounceProgressBar
转载请注明出处:http://blog.csdn.net/bbld_/article/details/41246247 [Rocko's blog] 之前几天下载了非常久没用了的桌面版酷狗来用用的时候 ...
随机推荐
- Reuse a SSL socket
It's possible to reuse a SSL socket after proper cleanup. See SSL Socket free and shutdown on stacko ...
- Linux 配置 nginx + php
为什么!!!我配过的服务器已经有5.6个了吧,为什么每一次配置都能要了我的老命??这次写清楚过程,以后再要被配服务器坑,我特么要砍人了. 提示:测试网站能否访问的时候,最好关掉浏览器的缓存功能或者勤清 ...
- Fiddler—重复发送一个请求的设置
https://jingyan.baidu.com/article/b2c186c829a85dc46ff6ff60.html 选中一个request——>Reissue Sequentaill ...
- bootstrap datatable 数据刷新问题
在项目中,页面初始化的时候,通过通过向后台请求数据,页面初始化完之后,datatable是有数据的,当我点击页面的搜索按钮(按照时间过滤数据),datatable的数据要能重新刷新或者重载:这一点,我 ...
- HDU_1180_诡异的楼梯_BFS
链接:http://acm.hdu.edu.cn/showproblem.php?pid=1180 诡异的楼梯 Time Limit: 2000/1000 MS (Java/Others) Me ...
- js异步请求
目前async / await特性并没有被添加到ES2016标准中,但不代表这些特性将来不会被加入到Javascript中.在我写这篇文章时,它已经到达第三版草案,并且正迅速的发展中.这些特性已经被I ...
- 梦想CAD控件安卓文字样式
增加文字样式 用户可以增加文字样式到数据库,并设置其字体等属性,具体实现代码如下: // 增加文字样式 //getCurrentDatabase()返回当前数据库对象 //getTextstyle() ...
- Oracle RAC 后台进程
LMS - Gobal 全局缓存服务进程 LMD - Global Enqueue Service Daemon 全局查询服务守护进程 LMON - 全局服务器监控进程 LCK0 ...
- 用Java写一个生产者-消费者队列
生产者消费者的模型作用 通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用. 解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系 ...
- Ztree加载完成后显示勾选节点
①前言:这个在度娘上没有搜到解决的办法,于是自己查看了ztree的API,发现其实还是比较简单的.做个笔记以备不时之需. ②需求: 像下图一样,在加载完成之后就显示需要勾选的项. ③解决方案: 首先页 ...