【Android自定义View 一】第一个自定义的控件

         自定义控件在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; // 用于控制SurfaceViewprivate 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(); // 得到surfaceHoldermHolder.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.*/@Overridepublic void surfaceCreated(SurfaceHolder holder) {Log.d("danxx", "surfaceCreated---->");// 获取屏幕宽高screenW = this.getWidth();sceenH = this.getHeight();qEndX = screenW - 20; // 贝塞尔曲线终点X轴坐标为距离右屏幕边20qEndY = qStartY + 40; // 贝塞尔曲线终点Y轴坐标为起点Y轴坐标加40qControlX = (qEndX - qStartX) / 2; // 贝塞尔曲线的控制点X坐标为起终X轴坐标的中间qCOntrolY = (qEndY - qStartY) / 2; // 贝塞尔曲线的控制点Y坐标为起终Y轴坐标的中间mThread = new Thread(this); // 创建线程对象flag = true; // 设置线程标识为truexReturn = 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.*/@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}/*** @param holder*            The SurfaceHolder whose surface is being destroyed.*/@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {flag = false; // 设置线程的标识为false}/*** 启动线程不断画*/@Overridepublic 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) {    //横坐标不返回
//判断图形横坐标是否小于屏幕宽度减去100if (mX < (screenW - 100)) {        //小于mX += 3;    //横坐标往右3} else {    //不小于xReturn = true;    //设置横坐标返回}} else {    //横坐标返回
//判断横坐标是否大于10if (mX > 10) {    //大于mX -= 3;    //横坐标往左3} else {    //不大于xReturn = false;    //设置横坐标不返回}}//判断贝塞尔曲线的控制点横坐标是否返回if (!cReturn) {    //控制点横坐标不返回
//判断控制点横坐标是否小于终点横坐标减3if (qControlX < (qEndX - 3)) {    //小于qControlX += 3;    //控制点横坐标往右3} else {    //不小于cReturn = true;    //设置控制点横坐标返回}} else {    //控制点横坐标返回
//判断控制点横坐标是否大于起点横坐标加3if (qControlX > (qStartX + 3)) {    //大于qControlX -= 3;    //控制点横坐标减3} else {    //不大于cReturn = false;    //设置控制点横坐标不返回}}}/*** 当屏幕被触摸时调用*/@Overridepublic 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);}}