定义自定义控件

[TOC]
安卓的ui元素全部都基于view或者是viewgroup。在一些app中我imenxuyao自定义view来满足我们的需求,这意味着对于现有的view的一些延伸创造view的子类以创造更加复杂的view。
自定义自己的view视图意味着扩展view或者一个存在的子类,然后能够重写view的某些行为例如onDrawonToutchEvent 然后在你的活动中使用。

创建完全自定义的组件

自定义组件我们主要着重于5个方面:
- Drawing: 控制市局上view的渲染,通过重写onDraw 方法
- Interaction: 控制用户和view的交互方式通过控制onTouchEvent 和手势
- Measurement: 控制view的内容区域通过重写onMeasure() 方法
- Attributes: 自定义vie的XML的属性,然后使用TypedArray控制相关的行为
- Persistence: 存储或者获得相关的状态以避免失去状态,方法:onSavedInstanceStateonRestoreInstanceState
下面我们将自定义一个试图展示不同的图形。

定义view类

为了实现一个可切换的图形选择器我们定义一个ShapeSelectorView 继承View 视图。
1. 建立一个ShapeSelectorView 类继承View类,并写明构造方法
2. 在布局文件中添加我们定义的view
3. 定义了两个属性app:shapeColorapp:displayShapeName
4. 新建values/attr.xml 文件定义视图的两个属性


<resources>
   <declare-styleable name="ShapeSelectorView">
       <attr name="shapeColor" format="color" />
       <attr name="displayShapeName" format="boolean" />
   declare-styleable>
resources>

5.提取视图的相关属性赋值给成员变量,使用TypedArrayobtainStyledAttributes 在AttributeSet上

public class ShapeSelectorView extends View {
  private int shapeColor;
  private boolean displayShapeName;

  public ShapeSelectorView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setupAttributes(attrs);
  }

  private void setupAttributes(AttributeSet attrs) {
    // Obtain a typed array of attributes
    TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ShapeSelectorView, 0, 0);
    // Extract custom attributes into member variables
    try {
      shapeColor = a.getColor(R.styleable.ShapeSelectorView_shapeColor, Color.BLACK);
      displayShapeName = a.getBoolean(R.styleable.ShapeSelectorView_displayShapeName, false);
    } finally {
      // TypedArray objects are shared and must be recycled.
      a.recycle();
    }
  }
}

6绘制一个形状

public class ShapeSelectorView extends View {
  // ...
  private int shapeWidth = 100;
  private int shapeHeight = 100;
  private int textXOffset = 0;
  private int textYOffset = 30;
  private Paint paintShape;

  // ...
  public ShapeSelectorView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setupAttributes(attrs);
    setupPaint();
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawRect(0, 0, shapeWidth, shapeHeight, paintShape);
    if (displayShapeName) {
      canvas.drawText("Square", shapeWidth + textXOffset, shapeHeight + textXOffset, paintShape);
    }
  }

  private void setupPaint() { 
      paintShape = new Paint();
      paintShape.setStyle(Style.FILL);
      paintShape.setColor(shapeColor);
      paintShape.setTextSize(30);
   }
}

整体的代码:

 "http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tvPrompt"
        >
        <com.codepath.shapeselector.ShapeSelectorView

            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/shapeSelector"
            app:shapeColor="#7f0000"
            app:displayShapeName="true"/>
    
public class ShapeSelectorView extends View {
    private int shapeColor;
    private boolean displayShapeName;
    private int shapeWidth = 100;
    private int shapeHeight = 100;
    private int textXOffset = 0;
    private int textYOffset = 30;
    private Paint paintShape;


    public boolean isDisplayShapeName() {
        return displayShapeName;
    }
//状态改变时需要重新绘制
    public void setDisplayShapeName(boolean displayShapeName) {
        this.displayShapeName = displayShapeName;
        invalidate();//状态改变时,使view无效?
        requestLayout();//当布局改变时调用
    }

    public int getShapeColor() {
        return shapeColor;
    }

    public void setShapeColor(int shapeColor) {
        this.shapeColor = shapeColor;
        invalidate();
        requestLayout();
    }
    //创建一个构造方法,包含父构造方法和属性集合和笔画的设置

    public ShapeSelectorView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //设置属性
        setupAttributes(attrs);
        setupPaint();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //参数分别为左上右下
        canvas.drawRect(0, 0, shapeWidth, shapeHeight, paintShape);
        if (displayShapeName) {
            canvas.drawText("Square", textXOffset, shapeHeight + textYOffset, paintShape);
        }
    }
    private void setupPaint() {
        paintShape = new Paint();
        paintShape.setStyle(Paint.Style.FILL);
        paintShape.setColor(shapeColor);
        paintShape.setTextSize(30);
    }
    //参数为一些属性的集合,可以与xml文件连接起来的属性
    private void setupAttributes(AttributeSet attrs) {
        //获得属性类型数组

        TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ShapeSelectorView, 0, 0);
        try {
            //提取属性作为成员变量
            shapeColor = a.getColor(R.styleable.ShapeSelectorView_shapeColor, Color.BLACK);
            displayShapeName = a.getBoolean(R.styleable.ShapeSelectorView_displayShapeName, false);
        } finally {
           a.recycle();//回收typedArray供以后使用
        }
    }



}

计算大小

//测量view和它的内容来确定高度和宽度
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //给名称定义额外的间距
        int textPadding = 10;
        int contentWidth = shapeWidth;
        //根据测量的最小值和测量规格确定宽度:内容宽度 + 左边距+右边距
        int minw = contentWidth + getPaddingLeft() + getPaddingRight();
        int w = resolveSizeAndState(minw, widthMeasureSpec, 0);
        //让view的获得足够的高度
        int minh = shapeHeight + getPaddingTop() + getPaddingBottom();
        if (displayShapeName) {
            //如果展示名称的话要加上文字高度
            minh += textYOffset + textPadding;
        }
        int h = resolveSizeAndState(minh,  heightMeasureSpec, 0);
        //调用此方法确定测量的宽度和高度
        //可以使用getMeasuredWidth和getMeasuredHeight获得值
        setMeasuredDimension(w, h);
    }

onMeasure 方法决定了基于内容的视图的高度和宽度,要记住计算包括了view的内间距和内容的大小,而且这个方法必须带哦用setMeasuredDimensionMeasureSpec 包含了父布局对子布局的限制,resolveSizeAndState 通过两方面进行比较返回一个恰当的值。

切换视图

我们想要每次按钮被点击的时候就会使图形被改变,因此需要onTouchEvent 方法处理点击事件,每次点击都会改变下标

public class ShapeSelectorView extends View {
  // ...
  private String[] shapeValues = { "square", "circle", "triangle" };
  private int currentShapeIndex = 0;

  // Change the currentShapeIndex whenever the shape is clicked
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    boolean result = super.onTouchEvent(event);
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
      currentShapeIndex =  (++currentShapeIndex) % shapeValues.length;
      postInvalidate();
      return true;
    }
    return result;
  }
}

下面我们重写onDraw 方法:

 @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    String shapeSelected = shapeValues[currentShapeIndex];
    if (shapeSelected.equals("square")) {
      canvas.drawRect(0, 0, shapeWidth, shapeHeight, paintShape);
      textXOffset = 0;
    } else if (shapeSelected.equals("circle")) {
      canvas.drawCircle(shapeWidth / 2, shapeHeight / 2, shapeWidth / 2, paintShape);
      textXOffset = 12;
    } else if (shapeSelected.equals("triangle")) {
      canvas.drawPath(getTrianglePath(), paintShape);
      textXOffset = 0;
    }
    if (displayShapeName) {
      canvas.drawText(shapeSelected, 0 + textXOffset, shapeHeight + textYOffset, paintShape);
    }
  }

  protected Path getTrianglePath() {
    Point p1 = new Point(0, shapeHeight), p2 = null, p3 = null;
    p2 = new Point(p1.x + shapeWidth, p1.y);
    p3 = new Point(p1.x + (shapeWidth / 2), p1.y - shapeHeight);//原点为屏幕左上角
    Path path = new Path();
    path.moveTo(p1.x, p1.y);
    path.lineTo(p2.x, p2.y);
    path.lineTo(p3.x, p3.y);
    return path;
  }

  // ...
}

然后就是对于在主视图中加入按钮点击后,弹出Toast,

保存view相应状态

 @Override
    protected Parcelable onSaveInstanceState() {
        //创建Bundle对象
        Bundle bundle = new Bundle();
        //存储基本view的状态
        bundle.putParcelable("instanceState", super.onSaveInstanceState());
        //存储自定义view的状态
        bundle.putInt("currentShapeIndex", this.currentShapeIndex);
        //如果有的话还应该存储其他的状态
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        //检测我们是否保存了状态
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            this.currentShapeIndex = bundle.getInt("currentShapeIndex");
            state = bundle.getParcelable("instanceState");
        }
        super.onRestoreInstanceState(state);
    }

你可能感兴趣的:(android学习)