在很多时候我们为获得在视图中自由绘制的能力, 需要创建一个继承于View类的定制类,然后重写onTouchEvent方法处理触摸时间,重写onDraw绘制自定义视觉效果。但这里可能会被一个问题困扰,那就是设备旋转导致数据丢失的问题,好在View类为我们提供了onSaveInstanceState和onRestoreInstanceState两个方法,虽然这两个方法和Activity两个方法很相似,但是千万别认为是一样的,因为他们的使用方法完全不同。
为了侧重数据保存的重点,我们这里简化了绘制的内容。下面定制的View子类功能是根据触摸事件发生的始末位置,绘制一组矩形。详细观察以下rectDrawingView定制类源码:
- package com.art.zok.rectdrawingview;
-
- import java.util.ArrayList;
-
- import android.annotation.SuppressLint;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Paint;
- import android.graphics.PointF;
- import android.graphics.RectF;
- import android.os.Bundle;
- import android.os.Parcelable;
- import android.util.AttributeSet;
- import android.view.MotionEvent;
- import android.view.View;
-
- public class RectDrawingView extends View {
- private static final String TAG = "RectDrawingView";
-
- private RectF mCurRect;
- private ArrayList<RectF> mRects = new ArrayList<RectF>();
- private Paint mRectPaint;
-
-
-
-
-
-
- public RectDrawingView(Context context) {
- super(context);
- }
-
-
-
-
-
-
-
- public RectDrawingView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mRectPaint = new Paint();
- mRectPaint.setColor(0x22ff0000);
- }
-
-
- @SuppressLint("ClickableViewAccessibility") @Override
- public boolean onTouchEvent(MotionEvent event) {
- PointF curr = new PointF(event.getX(), event.getY());
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mCurRect = new RectF();
- mCurRect.left = curr.x;
- mCurRect.top = curr.y;
- mRects.add(mCurRect);
- break;
- case MotionEvent.ACTION_MOVE:
- if(mCurRect != null) {
- mCurRect.right = curr.x;
- mCurRect.bottom = curr.y;
- invalidate();
- }
- break;
- case MotionEvent.ACTION_UP:
- mCurRect = null;
- break;
- case MotionEvent.ACTION_CANCEL:
- mCurRect = null;
- break;
- default:
- break;
- }
- return true;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- for(RectF rect : mRects) {
- float left = Math.min(rect.left, rect.right);
- float right = Math.max(rect.left, rect.right);
- float top = Math.min(rect.top, rect.bottom);
- float bottom = Math.max(rect.top, rect.bottom);
- canvas.drawRect(left, top, right, bottom, mRectPaint);
- }
-
- super.onDraw(canvas);
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- Bundle bundle = new Bundle();
- Parcelable superData = super.onSaveInstanceState();
- bundle.putParcelable("super_data", superData);
- bundle.putParcelableArrayList("save_data", mRects);
- return bundle;
- }
-
- @Override
- protected void onRestoreInstanceState(Parcelable state) {
- Bundle bundle = (Bundle) state;
- Parcelable superData = bundle.getParcelable("super_data");
- mRects = bundle.getParcelableArrayList("save_data");
- super.onRestoreInstanceState(superData);
- }
- }
目前不需要研究上述代码,只要获得RectDrawingView即可。然后再布局文件中静态创建该组件。
- <com.art.zok.rectdrawingview.RectDrawingView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/RectDrawingView"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
现在我们来解释上述代码,首先如果我们要从XML布局文件创建RectDrawingView类那么就必须必须创建一个RectDrawingView(Context context, AttributeSet attrs)类,因为Android在实例XML文件中的组件时是通过调用上述构造方法实现的。然而,RectDrawingView(Context context)构造方法一般也需要添加,因为说不定某些时候我们就需要从代码中动态创建组件,所以为了统一情况,我们在自定义View子类时都会创建以上两个构造方法。
接下来在XML布局文件中声明RectDrawingView组件,需特别注意的是,android:id属性必须声明,如果没有为组件添加id,那么重写的onSaveInstanceState和onRestoreInstanceState方法是不会调用的。
关于onTouchEvent和onDraw方法这里不详细说了,很简单,如果需要请自行百度。现在主要集中在onSaveInstanceState和onRestoreInstanceState方法上,首先这两个方法与Activiy像似的两个方法最大的区别在于参数与返回值,View使用Parcelable传递数据,而Activity使用Bundle传递数据,很明显Bundle传递数据更为简单和强大,如果使用Parcelable对象传递自定义数据就需要实现Parcelable接口和CREATE静态常量,不但复杂而且代码量也很大。然而我们这里使用的RectF类作为基础数据,RectF是Android提供的并且已经实现了Parcelable接口和CREATE常量,这方便了许多。但是在onSaveInstanceState方法中,我们必须将父类保存的数据也保存在parcelable对象中,否则应用将发生崩溃。我了避免再次自定义Parcelable子类,因此我们这里使用Bundle将superData = super.onSaveInstanceState();父类保存数据和mRects自定义数据保存在其中,由于Bundle也是实现了Parcelable接口和CARETE常量的,因此在View的两个方法中传递是没有问题的。在onRestoreInstanceState方法中,我们需要将state强制转化成Bundle对象,如果为了安全一点,这里可以使用类型检查,然后将superData从Bundle对象中取出,再使用superData作为参数调用super.onRestoreInstanceState(superData);,相同的mRects也从Bundle中取出进行初始化,需要注意一点,这里千万不能将参数state传递给super.onRestoreInstanceState(state);方法,否则应用同样会崩溃。
![](https://img-blog.csdn.net/20151204130110002?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)