定义自定义控件

[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 文件定义视图的两个属性

<?xml version="1.0" encoding="utf-8"?>
<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);
   }
}

整体的代码:

 <RelativeLayout xmlns:android="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"/>
    </RelativeLayout>
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);
    }
时间: 2024-11-09 00:16:30

定义自定义控件的相关文章

Android笔记(六十七) 自定义控件

实际编程中,系统提供的控件往往无法满足我们的需求,一来是样子丑陋,二来是一些复杂的组合需要多次使用的话,每次都写一堆控件的组合会很耗费时间,所以我们将这些组件的组合自定义为一个新的控件,以后使用的时候直接用该控件,方便又简单.最常见的例子就是软件中的titleTar 实现自定义控件的步骤: 1.设置控件的属性 2.实现我们的View 3.引用我们自定的View 官方文档:http://developer.android.com/training/custom-views/create-view.

Android自定义控件之滑动开关

自定义开关控件 Android自定义控件一般有三种方式 1.继承Android固有的控件,在Android原生控件的基础上,进行添加功能和逻辑. 2.继承ViewGroup,这类自定义控件是可以往自己的布局里面添加其他的子控件的. 3.继承View,这类自定义控件没有跟原生的控件有太多的相似的地方,也不需要在自己的肚子里添加其他的子控件. ToggleView自定义开关控件表征上没有跟Android原生的控件有什么相似的地方,而且在滑动的效果上也没有沿袭Android原生的地方,所以我们的自定义

VC++ 自定义控件的建立及使用方法

一.VC++定义自定义控件与delphi,VB有些差异. delphi,vb在 file-new-other中建立.vc++在工具栏中就有自定义控件,但必须加入控件类型. 许多书籍都在类向导中建立.我这里介绍的是手动建立,其结果是一样的.二.建立过自定义控件类型:   2.1.把工具栏上的自定义控件放入对话框中   2.2.建立Mycontrol.h, Mycontrol.cpp文件   2.3.Mycontrol.h中的定义是 #ifndef __MYCTROLTRL_H__ #define

《Android第一行代码》学习记录008 - 创建自定义控件

一.关于View与布局,首先上图 从图中可以看到: View是Android中最基本的UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这个区域的各种事件: ViewGroup是一种特殊的View,它可以包含很多子View和子ViewGroup,是一种用于放置控件和布局的容器: 我们所使用的所有控件都是直接或间接继承View的,各种控件其实就是在View的基础上添加了各自的功能: 所有布局都是直接继承自ViewGroup的: 二.定义自定义控件需要: 自定义控件的布局文件: 自定义控件的类: 以

背水一战 Windows 10 (78) - 自定义控件: 基础知识, 依赖属性, 附加属性

原文:背水一战 Windows 10 (78) - 自定义控件: 基础知识, 依赖属性, 附加属性 [源码下载] 作者:webabcd 介绍背水一战 Windows 10 之 控件(自定义控件) 自定义控件的基础知识,依赖属性和附加属性 示例演示自定义控件的基础知识,依赖属性和附加属性1.自定义控件的示例/MyControls/themes/generic.xaml <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/

Android动画:模拟开关按钮点击打开动画(属性动画之平移动画)

在Android里面,一些炫酷的动画确实是很吸引人的地方,让然看了就赏心悦目,一个好看的动画可能会提高用户对软件的使用率.另外说到动画,在Android里面支持两种动画:补间动画和属性动画,至于这两种动画的区别这里不再介绍,希望开发者都能在使用的过程中体会两者的不同. 本文使用属性动画完成,说到属性动画,肯定要提到 JakeWharton大神写的NineOldAndroids动画库,如果你的app需要在android3.0以下使用属性动画,那么这个库就很有作用了,如果只需要在高版本使用,那么直接

自定义View控件(1—xib实例代码)

/** * 1. 设置显示到控制器上的模型数据(根据plist文件设置其属性,然后定义与实现工厂方法来快速实现字典转模型) */   // 1. 模型Shop.h文件 @interface Shop : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) NSString *icon; - (Shop *)initWithDict:(NSDictionary *)dict; +

apkplug主题皮肤切换之自定义样式-05

本文基于apkplug V1.6.9讲解 一 何为apkplug自定义样式切换 apkplug主题切换包含两个层次 1.通用主题,与系统主题相似 2.自定义控件样式切换 根据上一篇文章讲解我们可以判定通用主题有其局限性,在很多情况下我们需要替换的是与我们应用本事相结合的自定义控件样式.如QQ聊天挂件 QQ皮肤,QQ聊天泡泡等.针对这种情况我们为apkplug新设计了一套接口,以满足开发者这方面的需求. 二 自定义控件样式切换 自定义控件样式,顾名思义是我们开发者要求的自定义,因为它与开发的应用本

WP8.1学习系列(第十九章)——事件和路由事件概述

我们将介绍在使用 C#.Visual Basic 或 Visual C++ 组件扩展 (C++/CX) 作为编程语言并使用 XAML 进行 UI 定义时,针对 Windows 运行时应用的事件的编程概念.你可以在 XAML 中的 UI 元素声明中为事件分配处理程序,或者在代码中添加处理程序.Windows 运行时支持路由事件:借助此功能,某些输入事件和数据事件可由引发该事件的对象以外的对象来处理.在定义控件模板或使用页面或版式容器时,路由事件十分有用. 事件即编程概念 通常而言,对 Window