做android开发到现在也有一小段时间了,知识确实在不断的累计,但是遗忘的却也是不少,有筒子会问那该怎么办嘞?呵呵 凉拌!我个人总结了下学习经验,1善于思考把各种知识串联起来,2敢于实践,有的人总以为这个模块的所有代码我都能看得懂就直接过了,其实看得懂并不代表你写的出来,3勤于提问,碰到问题首先要着手自己解决,其次借鉴他人,最后请教伙伴。如果对于所有知识都保持在一知半解的状态注定你是一辈子的代码搬运工!

        今天总结下如何实现自定义view,以前自己也写过一些简单的view但是没有系统的整理出来,随时用随时写,虽说并无影响但是并不符合java的基本思想,封装继承多态这些特性的优势可不只是说说而已,真的很便捷。前段日子看了一段关于自定义封装的视频感觉思路不错值得借鉴,现在就和大家一起分享下!

        对于自定义view首先就是先创建一个CustomView出来然后让他继承老祖宗View,之后系统会提示要实现构造方法,分为三种:第一种1个参数的多数用于代码中,第二种是2个参数的多数用于xml布局中,第三种是三个参数的多出一个defStyleAttr属性(默认样式)。在CustomView中一般来要重写两个方法onDraw和onLayout这两个方法前面的用于绘制后面的用于测量,如果自定义view和嵌套view有相同属性的话一般还回用到onTouchEvent和onInterceptTouchEvent两个事件,用于判断当前操作适用于哪个view。

        对于一个项目如果有很多的自定义view,我们可以CustomView进行一次简单的封装以便继承达到重复利用。直接贴代码:

public abstract class SimonView extends View {
     private MyThread myThread;
     protected long sleepTime;

     public SimonView(Context context) {
         super(context);
         // TODO Auto-generated constructor stub
     }

     public SimonView(Context context, AttributeSet attrs) {
         super(context, attrs);
         // TODO Auto-generated constructor stub
     }

     /**
      * 为了禁止子类更改onDraw方法要加final限制
      */
     @Override
     protected final void onDraw(Canvas canvas) {
         /**
          * 线程未开启->创建 开启->drawCanvas
          */
         if (myThread == null) {
             myThread = new MyThread();
             myThread.start();
         } else {
             drawCanvas(canvas);
         }
     }

     private boolean isRunning = true;

     class MyThread extends Thread {

         @Override
         public void run() {
             initParams();
             long workTime;
             while (isRunning) {
                 workTime = System.currentTimeMillis();
                 logic();
                 postInvalidate();// 重新调用onDraw方法
                 workTime = System.currentTimeMillis() - workTime;
                 // Log.i("Simon", "----workTime----" + workTime);
                 try {
                     if (workTime < sleepTime) {
                         Thread.sleep(sleepTime - workTime);
                     }
                     // Thread.sleep(sleepTime);
                 } catch (InterruptedException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 }
             }
         }
     }

     // 负责绘制
     protected abstract void drawCanvas(Canvas canvas);

     // 负责逻辑
     protected abstract void logic();

     // 初始化一些参数
     protected abstract void initParams();

     /**
      * 离开屏幕的时候关闭循环
      */
     @Override
     protected void onDetachedFromWindow() {
         // TODO Auto-generated method stub
         isRunning = false;
         super.onDetachedFromWindow();
     }
 }

上面这一段代码就是经过提取后的view,我们可以在其它CustomView中继承使用。那么其它CustomView怎么写呢,如果自定义的只是单一的view还好如果是很多数量的要怎么处理呢,接着看:我们以雨滴为例

public class RainView extends SimonView {
     private int rainCount;
     private int rainOffset;
     private ArrayList<RainItem> rainList = new ArrayList<RainItem>();

     public RainView(Context context) {
         super(context);

     }

     public RainView(Context context, AttributeSet attrs) {
         super(context, attrs);

     }

     /**
      * 设置雨滴数量
      * 
      * @param rainCount
      */
     public void setRainCount(int rainCount) {
         this.rainCount = rainCount;
     }

     /**
      * 设置雨滴偏移量 offset一般建议为1-5
      * 
      * @param rainOffset
      */
     public void setRainOffset(int rainOffset) {
         this.rainOffset = rainOffset;
     }

     @Override
     protected void drawCanvas(Canvas canvas) {
         for (RainItem rainItem : rainList) {
             rainItem.drawCanvas(canvas);
         }
     }

     @Override
     protected void logic() {
         for (RainItem rainItem : rainList) {
             rainItem.logic();
         }
     }

     @Override
     protected void initParams() {
         for (int i = 0; i < rainCount; i++) {
             RainItem rainItem = new RainItem(getWidth(), getHeight(),
                     rainOffset);
             rainList.add(rainItem);
         }
     }

 }


下面这段时抽出来的单个雨滴属性:

public class RainItem {
     private float startX;
     private float startY;
     private float stopX;
     private float stopY;
     private float offsetX;
     private float offsetY;
     private Paint paint = new Paint();
     private Random random = new Random();

     private int width;
     private int height;
     private int offset;
     private float opt;

     /**
      * 通过构造函数获得自定义view的width和height
      */
     public RainItem(int width, int height) {
         this.width = width;
         this.height = height;

         initItem();
     }

     /**
      * 通过构造函数获得自定义view的width和height offset一般建议为5-15,10为最佳
      * 
      * @param width
      * @param height
      * @param offset
      */
     public RainItem(int width, int height, int offset) {
         this.width = width;
         this.height = height;
         this.offset = offset;

         initItem();
     }

     /**
      * 初始化startX startY stopX stopY
      */
     private void initItem() {
         offsetX = 1 + random.nextInt(offset);
         offsetY = 10 + random.nextInt(offset * 2);
         startX = random.nextInt(width);
         startY = random.nextInt(height);
         stopX = startX + offsetX;
         stopY = startY + offsetY;
         // 控制速度opt
         opt = 0.1f + random.nextFloat();
     }

     public void drawCanvas(Canvas canvas) {
         // TODO Auto-generated method stub
         paint.setColor(Color.WHITE);
         canvas.drawLine(startX, startY, stopX, stopY, paint);
     }

     public void logic() {
         // TODO Auto-generated method stub
         startX += offsetX * opt;
         stopX += offsetX * opt;
         startY += offsetY * opt;
         stopY += offsetY * opt;
         if (startY > height) {
             initItem();
         }
     }
 }


答案很明显我们先对一个雨滴的属性进行分析,包括他的长度 速度 雨滴下落过程中的偏移量等等,之后封装一个雨滴的类提供给RainView来调用。事实上我们对于雨滴的数量和速度及偏移量等属性我们可以有两种设计方案,一种是:在values下创建attrs属性之后利用declare-styleable来定义我们的属性,之后要在我们RainView中的连个构造函数中取出属性利用TypeArray属性获取属性值,最后在xml布局中引用,值得注意的是在引用我们自定义属性的时候需要用到xmlns:Simon="http://schemas.android.com/apk/res/com.simon ,这个属性Simon为自定义的随意什么名字都可以,com.simon为你的包名,之后在你的自定义view中这样调用:Simon:Num="3"即可。另外一种就是通过在自定义View中设置setFunction()模式的属性,在调用的页面通过setFunction(xxx)属性来传递自定义view的一些基础属性,贴的就是这种方法。(个人推荐使用这种方法比较实用)

最后就是使用自定义view的时候要在xml中定义下:如

<com.simon.RainView
         android:id="@+id/rain_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         Simon:Num="3"
         android:background="#d5d5d5"/>


在MainActivity中设置属性:如

rainview = (RainView) findViewById(R.id.rainview);
         rainview.setRainCount(250);
         rainview.setRainOffset(3);



对于一些复杂问题的处理,建议大家采取分散方式处理,不要给我们这个单核的大脑太大的负担,举个简单的例子对于小孩子来讲最开始能理解运算10以内的加减法,如果你非得让他做10+10这个问题他一定会抓狂。但是这个问题也可以拆分一下 如10=5+5  5=1+1+1+1+1 看着感觉复杂了 但是实际却简单了 我们在处理一些运算据麻烦的效果的时候也可以采取这种思想。当然如果你做的多了后获取就可以直接忽略,就像你让你现在做10+10的运算还会难得住你吗,是不是妙算☺