Android自定义View初步

一篇适合初学者的文章,作者居然是我的校友,大二学生。

 

*-------正文--------*

有关使用Android如何设计出有个性的界面,按照本人估计,除了遵循google的设计规范,就只能使用自定义View这个最灵活的方式了,这几天找了些资料学习自定义View,但是学习android developer文档中自定义的View比较麻烦,又找了些比较简单的材料,结合自己对CustomView这个实例的理解,开始学习自定义View。

 

 

下面实现一个类似时钟/仪表盘的简单界面,通过绘制一个圆来实现,这个圆周围有标的刻度,同时在每五个位置上绘制一个比其他刻度线长的刻度,然后再绘制一个类似的表针。我来一步一步实现这个界面,同时我会介绍如何让View自适应界面,在LinearLayout中添加多个此View的方法(暂时不了解ViewGroup原理,所以解决方式可能比较麻烦)。

首先上图,分别显示在横屏和宽屏下的效果:

 

 

对于这个自定义View,由于暂时不打算加入监听事件,所以只是使用XML进行静态定义,没有监听事件。

我按照步骤来:

  1. 定义一个自定义View类,继承自View类,同时注意必须使用构造方法,在构造方法中首先调用超类的构造方法:

同时注意,我无法添加IDE提示的第四种被重载的方法,就是四个参数的那个构造方法,是因为四个参数版本支持的API是21+。

 

2.下面实现的就是如何定义一些通过XML定义的参数,比如我们在XML布局下使用的id,layout_width,layout_height等等参数是如何定义的?我们就实现这种类型参数的定义。

为此,我定义一个专门用来保存这个名为attr资源的文件,按照标准,命名为attrs.xml

,然后在这个文件中定义一个针对我们自定义View的可用参数名的表,我为了这个View定义的是笔触的颜色border_color,还有笔触的宽度border_width。(为什么取这个名字呢,因为这是参考一个名为CircleImageView的时候定义的参数,但是暂时没实现出来,而是只学习了一部分,所以我没再改过)

 

因为这个参数要求整体唯一,所以如果你定义的一些attr在你引入的某些库中已经存在,会出现错误。

 

3.定义好了可选参数之后,不着急如何在XML布局中使用,先要知道如何在类中使用他们,我们在构造方法中获取并赋给一些自定义View中变量。按照官方文档的做法,需要在两个参数的构造方法中获取他们:

这样,我们就可以在XML布局中设置参数值,然后在此处会被获取。

 

4.我这个自定义View做重要的工作,我觉得应当是在onDraw和onSizeChanged方法中,下面介绍这两个重写的方法作用:

  onDraw(Canvas canvas)方法

这个方法是用来绘制View这个Canvas用的重要方法,所有canvas绘图的方法都适用于此,在此介绍一个介绍这个的文章,

地址在此:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html

 

  onSizeChanged()

这个方法在界面的尺寸更改的时候会被调用,一般是在屏幕旋转的时候会被调用,有两个新w/h和旧w/h会被传入,这里我用来实现初始化绘图的时候边框(RecF类型,具体见Canvas绘图中的Oval和矩形绘制类)等。

 

5.说完了两个方法的重要性,开始界面绘制的重要工作,因为我们绘制的都是比较简单的线条,我们只需要一个画笔,同时为了方便使用整个View的界面中的一部分,我使用了一个RecF来定义一块矩形区域。绘制圆形使用的radius,每个标度使用Line来绘制,同时表针使用的也是Line。。。这个部分可以自行决定何种算法绘制,这里介绍直接计算的方法,另一种方式在上面介绍的Canvas的文章中介绍过,就是使用旋转进行绘制,下面是我的主要实现

1)首先初始化画笔

自定义一个init方法用来在三个构造方法中都初始化画笔:

 

2)初始化全局参数,主要是在onSizeChanged()中,这是为了自适应界面,每次界面变化的时候都会被调用来重新计算一些参数:

 

3)在onDraw中绘制:

  1. @Override
  2.     protected void onDraw(Canvas canvas) {
  3.         super.onDraw(canvas);
  4.         canvas.drawColor(0xff000000);
  5.         mPaint.setColor(0x66555555);
  6.         canvas.drawRoundRect(new RectF(mBounds.centerX()-(float)0.9*width/2, mBounds.centerY() - (float)0.9*height/2, mBounds.centerX() + (float)0.9*width/2, mBounds.centerY() + (float)0.9*height/2), 30, 30, mPaint);
  7.         mPaint.setColor(mBorderColor);
  8.         canvas.drawCircle(mBounds.centerX(),mBounds.centerY(),radius,mPaint);
  9.         float start_x,start_y;
  10.         float end_x,end_y;
  11.         for(int i=0;i<60;++i){
  12.             start_x= radius *(float)Math.cos(Math.PI/180 * i * 6);
  13.             start_y= radius *(float)Math.sin(Math.PI/180 * i * 6);
  14.             if(i%5==0){
  15.                 end_x = start_x+largeLength*(float)Math.cos(Math.PI / 180 * i * 6);
  16.                 end_y = start_y+largeLength*(float)Math.sin(Math.PI/180 * i * 6);
  17.             }else{
  18.                 end_x = start_x+smallLength*(float)Math.cos(Math.PI/180 * i * 6);
  19.                 end_y = start_y+smallLength*(float)Math.sin(Math.PI/180 * i * 6);
  20.             }
  21.             start_x+=mBounds.centerX();
  22.             end_x+=mBounds.centerX();
  23.             start_y+=mBounds.centerY();
  24.             end_y+=mBounds.centerY();
  25.             canvas.drawLine(start_x, start_y, end_x, end_y, mPaint);
  26.         }
  27.         canvas.drawCircle(mBounds.centerX(),mBounds.centerY(),20,mPaint);
  28.         canvas.rotate(60,mBounds.centerX(),mBounds.centerY());
  29.         canvas.drawLine(mBounds.centerX(),mBounds.centerY(),mBounds.centerX(),mBounds.centerY()-radius,mPaint);
  30.     }

这样我们就可以在XML布局中使用了,比如这样

  1. <LinearLayout
  2.     android:layout_height="match_parent"
  3.     android:layout_width="match_parent"
  4.     android:orientation="vertical"
  5.     xmlns:android="http://schemas.android.com/apk/res/android"
  6.     xmlns:app="http://schemas.android.com/apk/res-auto">
  7.  
  8.     <com.example.androidviewtest.view.CustomView
  9.         android:id="@+id/view1"
  10.         android:layout_width="match_parent"
  11.         android:layout_height="match_parent"
  12.         app:border_color="#ff1ff41f"
  13.         app:border_width="2dp"/>
  14.  
  15. </LinearLayout>

显示的是:

 

 

6.解决多个LinearLayout下使用这个自定义View的方法。

在上面例子中,我们使用的虽然也是使用LinearLayout,但是在其中插入多个这样的自定义View的时候会出现问题:

 

我的解决方法是将自定义View用FrameLayout包装起来,代码如下:

 

结果是:

  1. <LinearLayout
  2.     android:layout_height="match_parent"
  3.     android:layout_width="match_parent"
  4.     android:orientation="vertical"
  5.     xmlns:android="http://schemas.android.com/apk/res/android"
  6.     xmlns:app="http://schemas.android.com/apk/res-auto">
  7.  
  8.     <FrameLayout
  9.         android:layout_width="match_parent"
  10.         android:layout_height="0dp"
  11.         android:layout_weight="1">
  12.         <com.example.androidviewtest.view.CustomView
  13.             android:id="@+id/view1"
  14.             android:layout_width="match_parent"
  15.             android:layout_height="match_parent"
  16.             app:border_color="#ff1ff41f"
  17.             app:border_width="2dp"/>
  18.     </FrameLayout>
  19.     <FrameLayout
  20.         android:layout_width="match_parent"
  21.         android:layout_height="0dp"
  22.         android:layout_weight="1">
  23.         <com.example.androidviewtest.view.CustomView
  24.             android:id="@+id/view2"
  25.             android:layout_width="match_parent"
  26.             android:layout_height="match_parent"
  27.             app:border_color="#ff2523f4"
  28.             app:border_width="2dp"/>
  29.     </FrameLayout>
  30. </LinearLayout>


 

这样我们就解决了这个不显示的问题,据了解我觉得可能是因为LinearLayout的实现中,结合我这个自定义View会有问题,这个问题跟我View中的某些应当定义的重写方法(大胆猜测是onMeasure)有关

 

上述代码运行在模拟器上,横屏和竖屏的结果类似开头的图。

 

整个View的所有代码如下:

  1. package com.example.androidviewtest.view;
  2.  
  3. import android.content.Context;
  4. import android.content.res.TypedArray;
  5. import android.graphics.Canvas;
  6. import android.graphics.Paint;
  7. import android.graphics.RectF;
  8. import android.util.AttributeSet;
  9. import android.view.View;
  10.  
  11. import com.example.androidviewtest.R;
  12.  
  13. public class CustomView extends View {
  14.  
  15.     private float mBorderWidth;
  16.     private int mBorderColor;
  17.  
  18.     private Paint mPaint;
  19.  
  20.     private RectF mBounds;
  21.     private float width;
  22.     private float height;
  23.     float radius;
  24.     float smallLength;
  25.     float largeLength;
  26.  
  27.     public CustomView(Context context) {
  28.         super(context);
  29.         init();
  30.     }
  31.  
  32.     public CustomView(Context context, AttributeSet attrs) {
  33.         super(context, attrs);
  34.  
  35.         TypedArray typedArray = context.getTheme()
  36.                 .obtainStyledAttributes(
  37.                         attrs,
  38.                         R.styleable.CustomView
  39.                         , 0, 0);
  40.  
  41.         try{
  42.             mBorderColor = typedArray.getColor(R.styleable.CustomView_border_color,0xff000000);
  43.             mBorderWidth = typedArray.getDimension(R.styleable.CustomView_border_width,2);
  44.         }finally {
  45.             typedArray.recycle();
  46.         }
  47.  
  48.         init();
  49.     }
  50.  
  51.     public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
  52.         super(context, attrs, defStyleAttr);
  53.         init();
  54.     }
  55.  
  56.     private void init(){
  57.         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  58.         mPaint.setStyle(Paint.Style.STROKE);
  59.         mPaint.setStrokeWidth(mBorderWidth);
  60.         mPaint.setColor(mBorderColor);
  61.     }
  62.  
  63.     @Override
  64.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  65.         super.onSizeChanged(w, h, oldw, oldh);
  66.  
  67.         mBounds = new RectF(getLeft(),getTop(),getRight(),getBottom());
  68.  
  69.         width = mBounds.right - mBounds.left;
  70.         height = mBounds.bottom - mBounds.top;
  71.  
  72.         if(width<height){
  73.             radius = width/4;
  74.         }else{
  75.             radius = height/4;
  76.         }
  77.  
  78.         smallLength =10;
  79.         largeLength=20;
  80.     }
  81.  
  82.     @Override
  83.     protected void onDraw(Canvas canvas) {
  84.         super.onDraw(canvas);
  85.         canvas.drawColor(0xff000000);
  86.         mPaint.setColor(0x66555555);
  87.         canvas.drawRoundRect(new RectF(mBounds.centerX()-(float)0.9*width/2, mBounds.centerY() - (float)0.9*height/2, mBounds.centerX() + (float)0.9*width/2, mBounds.centerY() + (float)0.9*height/2), 30, 30, mPaint);
  88.         mPaint.setColor(mBorderColor);
  89.         canvas.drawCircle(mBounds.centerX(),mBounds.centerY(),radius,mPaint);
  90.         float start_x,start_y;
  91.         float end_x,end_y;
  92.         for(int i=0;i<60;++i){
  93.             start_x= radius *(float)Math.cos(Math.PI/180 * i * 6);
  94.             start_y= radius *(float)Math.sin(Math.PI/180 * i * 6);
  95.             if(i%5==0){
  96.                 end_x = start_x+largeLength*(float)Math.cos(Math.PI / 180 * i * 6);
  97.                 end_y = start_y+largeLength*(float)Math.sin(Math.PI/180 * i * 6);
  98.             }else{
  99.                 end_x = start_x+smallLength*(float)Math.cos(Math.PI/180 * i * 6);
  100.                 end_y = start_y+smallLength*(float)Math.sin(Math.PI/180 * i * 6);
  101.             }
  102.             start_x+=mBounds.centerX();
  103.             end_x+=mBounds.centerX();
  104.             start_y+=mBounds.centerY();
  105.             end_y+=mBounds.centerY();
  106.             canvas.drawLine(start_x, start_y, end_x, end_y, mPaint);
  107.         }
  108.         canvas.drawCircle(mBounds.centerX(),mBounds.centerY(),20,mPaint);
  109.         canvas.rotate(60,mBounds.centerX(),mBounds.centerY());
  110.         canvas.drawLine(mBounds.centerX(),mBounds.centerY(),mBounds.centerX(),mBounds.centerY()-radius,mPaint);
  111.     }
  112. }

下一步我准备学习给这个View加入事件监听,我的目标是制作一个指南针式的应用。

posted @ 2017-03-13 12:59  天涯海角路  阅读(166)  评论(0)    收藏  举报