Android自定义View基础
自定义控件, 视频教程
http://www.jikexueyuan.com/course/1748.html
1. 编写自定义view
2. 加入逻辑线程
3. 提取和封装自定义view
4. 利用xml中定义样式来影响显示效果
工程代码 DIYControls.zip
----------------------------------
1. 编写自定义view
定义MyView
public class MyView extends View { public MyView(Context context, AttributeSet attrs) { super(context, attrs); } public MyView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { // 加入绘制元素 Paint paint = new Paint(); paint.setTextSize(30); canvas.drawText("hello carloz", 0, 30, paint); //默认左下对齐 } }
在布局文件中使用,背景绿色
<com.carloz.diycontrols.MyView android:layout_width="match_parent" android:layout_height="match_parent" android:background="#00ff00"/>
运行效果
绘制几何图形
canvas.drawLine(0, 60, 100, 60, paint); //绘制直线 Rect r = new Rect(10, 90, 110, 190); //parm: int canvas.drawRect(r, paint); // 绘制矩形 RectF rect = new RectF(10, 90, 110, 190); //parm: float canvas.drawRect(rect, paint); // 绘制矩形
绘制图片
public class MyView extends View { Bitmap bitmap; public MyView(Context context, AttributeSet attrs) { super(context, attrs); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); } public MyView(Context context) { super(context); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); } @Override protected void onDraw(Canvas canvas) { // 加入绘制元素 Paint paint = new Paint(); paint.setTextSize(30); canvas.drawText("hello carloz", 0, 30, paint); //默认左下对齐 RectF r = new RectF(10, 90, 110, 190); //parm: int canvas.drawRoundRect(r, 10, 10, paint); // 绘制圆角矩形 paint.setColor(Color.RED); //改变图形颜色 canvas.drawCircle(60, 270, 50, paint); // 绘制圆, 圆心, 半径 paint.setStyle(Style.STROKE); //绘制空心的元素 canvas.drawBitmap(bitmap, 10, 350, paint); //画图 } }
充当ContentView,可以看到,layout文件中的背景色已经去掉了
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); setContentView(new MyView(this)); } }
2. 加入逻辑线程
目的:让绘制元素动起来
2.1 跑马灯效果的 文字
public class LogicView extends View { Paint paint = new Paint(); String text = "Carloz Logic View"; private float rx = 0; MyThread thread; public LogicView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } public LogicView(Context context) { super(context); // TODO Auto-generated constructor stub } @Override protected void onDraw(Canvas canvas) { paint.setTextSize(30); canvas.drawText(text, rx, 30, paint); if(thread == null ) { thread = new MyThread(); thread.start(); } } class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub super.run(); while(true) { rx += 5; if (rx > getWidth()) //超出屏幕的时候让它返回来 rx = 0 - paint.measureText(text); postInvalidate(); //线程中更新绘制,重新调用onDraw方法 try { Thread.sleep(50); //速度太快肉眼看不到,要睡眠 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
效果如下,
2.2 扇形成圆
绘制颜色不断变化的圆
public class CircleView extends View { Paint paint = new Paint(); MyThread thread; private RectF rectF = new RectF(30, 30, 100, 100); private float sweepAngle = 0f; //区间角度 public CircleView(Context context, AttributeSet attrs) { super(context, attrs); } public CircleView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { canvas.drawArc(rectF, 0, sweepAngle, true, paint); //startAngle 起始角度, sweepAngle 区间角度, useCenter if(thread == null ) { thread = new MyThread(); thread.start(); } } class MyThread extends Thread { Random rand = new Random(); @Override public void run() { // TODO Auto-generated method stub super.run(); while(true) { sweepAngle += 2; if (sweepAngle > 360) sweepAngle = 0; int r = rand.nextInt(256); //0~255 int g = rand.nextInt(256); int b = rand.nextInt(256); paint.setARGB(255, r, g, b); //透明度为0, 随时改变颜色 postInvalidate(); //线程中更新绘制,重新调用onDraw方法 try { Thread.sleep(50); //速度太快肉眼看不到,要睡眠 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
<com.carloz.diycontrols.logicview.CircleView android:layout_width="match_parent" android:layout_height="match_parent" />
效果如下,
3. 提取和封装自定义view
3.1 简化代码逻辑 - 将操作提取封装成抽象方法,让子类实现
3.2 如何禁止子类修改操作 - 在方法前添加final关键字,不允许子类覆盖
public abstract class BaseView extends View { Thread thread; public BaseView(Context context, AttributeSet attrs) { super(context, attrs); } public BaseView(Context context) { super(context); } @Override final protected void onDraw(Canvas canvas) { //禁止子类覆盖,用final if(thread == null ) { thread = new MyThread(); thread.start(); } else{ drawSub(canvas); } } protected abstract void logic(); protected abstract void drawSub(Canvas canvas); @Override final protected void onDetachedFromWindow() { // 离开屏幕时结束 //onDetachedFromWindow在销毁资源(既销毁view)之后调用 running = false; super.onDetachedFromWindow(); } private boolean running = true; class MyThread extends Thread { @Override public void run() { while(running) { logic(); postInvalidate(); //线程中更新绘制,重新调用onDraw方法 try { Thread.sleep(50); //速度太快肉眼看不到,要睡眠 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
public class LogicView extends BaseView{ Paint paint = new Paint(); private RectF rectF = new RectF(30, 30, 100, 100); private float sweepAngle = 0; //区间角度 Random rand = new Random(); public LogicView(Context context, AttributeSet attrs) { super(context, attrs); } public LogicView(Context context) { super(context); } @Override protected void drawSub(Canvas canvas) { canvas.drawArc(rectF, 0, sweepAngle, true, paint); //startAngle 起始角度, sweepAngle 区间角度, useCenter } @Override protected void logic() { sweepAngle += 2; if (sweepAngle > 360) sweepAngle = 0; int r = rand.nextInt(256); //0~255 int g = rand.nextInt(256); int b = rand.nextInt(256); paint.setARGB(255, r, g, b); //透明度为0, 随时改变颜色 } }
运行如上代码可以看到,效果,与2是一样的
4. 利用xml中定义样式来控制显示效果
控制 文字行数,是否滚动
4.1 集成3中的BaseView定义控件 NumText,特别注意两个属性
private int lineNum = 0; boolean xScroll = false; //在需要覆盖的方法中,使用属性控制 显示 @Override protected void logic() { if(xScroll) { mx += 3; if (mx > getWidth()) mx = 0- paint.measureText(text); } } @Override protected void drawSub(Canvas canvas) { for(int i=0; i< lineNum; i++){ int textSize = 30 + i; paint.setTextSize(textSize); canvas.drawText(text, mx, textSize*(1+i), paint); } }
4.2 在res/values/attrs.xml中定义 styleable
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="NumText"> <attr name="lineNum" format="integer" /> <attr name="xScroll" format="boolean" /> </declare-styleable> </resources>
4.3 在需要使用该 自定义控件 的布局文件中
* 定义命名空间
xmlns:carloz="http://schemas.android.com/apk/res/com.carloz.diycontrols"
· carloz 命名空间,随意
· http://schemas.android.com/apk/res/ 是固定的
· com.carloz.diycontrols是包名
* 使用属性
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:carloz="http://schemas.android.com/apk/res/com.carloz.diycontrols" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/container" > <com.carloz.diycontrols.v4.NumText android:layout_width="match_parent" android:layout_height="match_parent" carloz:lineNum="3" carloz:xScroll="false" /> </FrameLayout>
4.4 在控件的构造函数中 加载 被定义的属性
public NumText(Context context, AttributeSet attrs) { super(context, attrs); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumText); //获取样式属性 lineNum = ta.getInt(R.styleable.NumText_lineNum, 1); xScroll = ta.getBoolean(R.styleable.NumText_xScroll, false); Log.i("ZXQ", "lineNum = " + lineNum); ta.recycle(); //释放TypedArray }
4.5 最终效果如下
工程代码 DIYControls.zip
Android的粒子和动画效果系列课程