自定义控件在Android开发中算是一个难点,很多人不愿去学习这个,但是在面试的时候你能把自定义控件玩得很6,那就是非常加分的一个地方了,就算是在平时的开发中,常常遇到原生的控件无法实现我们想要的想过,那么这时候就必须自定义控件了。自己在这方面还不是很擅长,准备花两个星期的时间来学习一下,跟大家一起分享!
可以说重载onMeasure(),onLayout(),onDraw()三个函数构建了自定义View的外观形象。再加上onTouchEvent()等重载视图的行为,可以构建任何我们需要的可感知到的自定义View。 别人说的一句话,这一句话总结了自定义控件。
我们知道,不管是自定义View还是系统提供的TextView这些,它们都必须放置在LinearLayout等一些ViewGroup中,因此理论上我们可以很好的理解onMeasure(),onLayout(),onDraw()这三个函数:1.View本身大小多少,这由onMeasure()决定;2.View在ViewGroup中的位置如何,这由onLayout()决定;3.绘制View,onDraw()定义了如何绘制这个View。下面是我觉得写得很好的几篇讲到自定义view的博客:
Android中Canvas绘图基础详解(附源码下载)
Android自定义View示例(二)—滑动开关
Android SurfaceView实战 打造抽奖转盘
自定义View之onMeasure()
下面是最基础的View绘制:
package org.example.gridview; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; /** * Created by Danxingxi on 2015/12/8. */ public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { /** * SurfaceView相对于View,SurfaceView的UI更新不在UI线程, 对于需要经常刷新UI的绘图使用Surface很好 */ private SurfaceHolder mHolder; // 用于控制SurfaceView private Canvas mCanvas; // 声明画布 private Paint mPaint, qPaint; // 声明两只画笔 private Path mPath, qPath, tPath; // 声明三条路径 private int mX, mY; // 坐标位置 /** * 坐标分为绘图坐标和canvas坐标,绘图坐标相当于我们的前后左右,跟着我们移动位置的变化而变化, * canvas相当于地球的东南西北,是不会变化的,自己体会一下,在游戏开发中有三种坐标,更为复杂 */ // 分别 代表贝塞尔曲线的开始坐标,结束坐标,控制点坐标,大学高数没学好,哭瞎!! private int qStartX, qStartY, qEndX, qEndY, qControlX, qCOntrolY; private int screenW, sceenH; // 屏幕的宽高 private Thread mThread; // 生明一个线程 // flag用于线程的标识,xReturn用于标识图形坐标是否返回,cReturn用于标识贝塞尔曲线的控制点坐标是否返回 private boolean flag, xReturn, cReturn; public MySurfaceView(Context context) { super(context); initView(context); } public MySurfaceView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } /** * 初始化一些控件 * * @param mContext */ private void initView(Context mContext) { Log.d("danxx", "initView---->"); mHolder = this.getHolder(); // 得到surfaceHolder mHolder.addCallback(this); mPaint = new Paint(); // 创建一支画笔 mPaint.setColor(Color.BLUE); // 设置画笔的颜色 qPaint = new Paint(); // 创建另一支画笔 qPaint.setColor(Color.GREEN); // 设置颜色 qPaint.setStyle(Paint.Style.STROKE);// 画笔类型为描边 qPaint.setStrokeWidth(3); // 设置描边宽度 qPaint.setAntiAlias(true); // 消除锯齿 // 设置Canvas坐标,左上角为(0,0) mX = 50; mY = 50; // 设置贝塞尔曲线的其实坐标(canvas坐标) qStartX = 60; qStartY = 60; // 创建路径对象 mPath = new Path(); qPath = new Path(); tPath = new Path(); // 设置可以被响应点击事件 setClickable(true); setFocusableInTouchMode(true); } /******** * 下面三个方法是我们实现了SurfaceHolder.Callback接口需要实现的方法,surfaceview的生命周期函数 **************/ /** * @param holder * The SurfaceHolder whose surface is being created. */ @Override public void surfaceCreated(SurfaceHolder holder) { Log.d("danxx", "surfaceCreated---->"); // 获取屏幕宽高 screenW = this.getWidth(); sceenH = this.getHeight(); qEndX = screenW - 20; // 贝塞尔曲线终点X轴坐标为距离右屏幕边20 qEndY = qStartY + 40; // 贝塞尔曲线终点Y轴坐标为起点Y轴坐标加40 qControlX = (qEndX - qStartX) / 2; // 贝塞尔曲线的控制点X坐标为起终X轴坐标的中间 qCOntrolY = (qEndY - qStartY) / 2; // 贝塞尔曲线的控制点Y坐标为起终Y轴坐标的中间 mThread = new Thread(this); // 创建线程对象 flag = true; // 设置线程标识为true xReturn = false; // 设置图形坐标不返回 cReturn = false; // 设置贝塞尔曲线控制点坐标不返回 mThread.start(); // 启动线程 } /** * @param holder * The SurfaceHolder whose surface has changed. * @param format * The new PixelFormat of the surface. * @param width * The new width of the surface. * @param height * The new height of the surface. */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } /** * @param holder * The SurfaceHolder whose surface is being destroyed. */ @Override public void surfaceDestroyed(SurfaceHolder holder) { flag = false; // 设置线程的标识为false } /** * 启动线程不断画 */ @Override public void run() { while (flag) { mDraw(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } private void mDraw() { mCanvas = mHolder.lockCanvas(); // 回去SurfaceView自带的Canvas画布 mCanvas.drawColor(Color.WHITE); // 设置画布颜色 drawMethod(mCanvas); mHolder.unlockCanvasAndPost(mCanvas); // 把画布显示在屏幕上 } private void drawMethod(Canvas mCanvas){ Log.d("danxx", "drawMethod---->"); //填充弧都在在一个矩形区域里面画 RectF mArea = new RectF(mX+200, mY+200, mX+200+100, mY+200+100); //在矩形区域内画填充弧,(矩形区域,其实角度,结束角度, ,画笔) X轴顺时针方向 mCanvas.drawArc(mArea, 0, 270, true, mPaint); //画图片 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); mCanvas.drawBitmap(bitmap, mX+20, mY+100, mPaint); //画圆 mCanvas.drawCircle(mX+40, mY+60, 30, qPaint); //画直线,两个点确定一条直线 mCanvas.drawLine(mX, mY+350, mX+500, mY+300, mPaint); mPath.reset(); //置空路径 mPath.moveTo(mX, mY+550); //第一个点,起点 mPath.lineTo(mX+100, mY+850); //第二个点 mPath.lineTo(mX+800, mY+850); //第二个点 //画路径,三点连起来就成一个三角形 mCanvas.drawPath(mPath, mPaint); //画贝塞尔曲线 qPath.reset(); qPath.moveTo(mX, mY+900); qPath.quadTo(qControlX, qCOntrolY, qEndX, 900); mCanvas.drawPath(qPath, qPaint); } /** * 自定义游戏逻辑方法 */ public void mGameLogic() { //判断图形横坐标是否返回 if (!xReturn) { //横坐标不返回 //判断图形横坐标是否小于屏幕宽度减去100 if (mX < (screenW - 100)) { //小于 mX += 3; //横坐标往右3 } else { //不小于 xReturn = true; //设置横坐标返回 } } else { //横坐标返回 //判断横坐标是否大于10 if (mX > 10) { //大于 mX -= 3; //横坐标往左3 } else { //不大于 xReturn = false; //设置横坐标不返回 } } //判断贝塞尔曲线的控制点横坐标是否返回 if (!cReturn) { //控制点横坐标不返回 //判断控制点横坐标是否小于终点横坐标减3 if (qControlX < (qEndX - 3)) { //小于 qControlX += 3; //控制点横坐标往右3 } else { //不小于 cReturn = true; //设置控制点横坐标返回 } } else { //控制点横坐标返回 //判断控制点横坐标是否大于起点横坐标加3 if (qControlX > (qStartX + 3)) { //大于 qControlX -= 3; //控制点横坐标减3 } else { //不大于 cReturn = false; //设置控制点横坐标不返回 } } } /** * 当屏幕被触摸时调用 */ @Override public boolean onTouchEvent(MotionEvent event) { //设置贝塞尔曲线的坐标为触摸时坐标 qStartX = (int) event.getX(); qStartY = (int) event.getY(); //设置贝塞尔曲线的控制点坐标为终点坐标与起点坐标对应的差值的一半,注意这里不是曲线的中点 qControlX = Math.abs(qEndX - qStartX) / 2; qCOntrolY = Math.abs(qEndY - qStartY) / 2; //设置控制点的横坐标不返回 cReturn = false; return super.onTouchEvent(event); } }