《Android编程权威指南》之定制视图与触摸事件

最近学习《Android编程权威指南》这本书,很不错的一本书哟~ 看了真的很是受益匪浅,为了让书中的例子以及技术知识点完全融入自身技能,手动敲书中的例子代码以及作总结笔记是很有必要的事情,嘻嘻嘻~ 下面开始啦!

这一章的Demo主要是为了学习处理触摸事件,所以定制了一个View响应用户的触摸与拖动,在屏幕上绘制出矩形框。最终的Demo截图:

Demo截图
实际上,此章内容就是在描述自定义View的一些基础知识来着,android中自带了很多优秀的视图与组件,但为了追求独特的应用视觉效果,根据项目需求,我们还是需要自定义一些视图。

创建定制视图

定制视图两大类别:

  1. 简单视图。也可能复杂,不包括子视图,几乎总是用来处理定制绘制。
  2. 聚合视图。由其他视图对象组成,通常管理子视图,不负责执行绘制,绘制工作都是委托给了各个子视图。

定制视图三大步骤:

  1. 选择超类,继承View或者FrameLayout等等。
  2. 继承选定的超类,覆盖超类构造方法。
  3. 覆盖其他关键方法,以定制视图行为。

整本书的Demo在设计过程中,都是推荐使用Fragment的,而且导入是v4包中的fragment,当然也是为了兼容。

SingleFragmentActivity.java

public abstract class SingleFragmentActivity extends AppCompatActivity {
    protected abstract Fragment createFragment();
    @LayoutRes
    protected int getLayoutRedId(){
        return R.layout.activity_fragment;
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutRedId());
        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);
        if (fragment == null){
            fragment = createFragment();
            fm.beginTransaction().add(R.id.fragment_container, fragment).commit();
        }
    }
}

activity_fragment.xml

Demo截图
DragAndDrawActivity.java

public class DragAndDrawActivity extends SingleFragmentActivity {
    @Override
    protected Fragment createFragment() {
        return DragAndDrawFragment.newInstance();
    }
}

DragAndDrawFragment.java

public class DragAndDrawFragment extends Fragment {
    public static DragAndDrawFragment newInstance(){
        return new DragAndDrawFragment();
    }
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_drag_and_draw, container, false);
        return v;
    }
}

fragment_drag_and_draw.xml(注意,使用BoxDrawingView的全路径名,这样布局inflater才能正确的解析布局XML文件,并按视图定义创建View实例,如果目标文件放置在其他保重,布局inflater无法找到目标会导致奔溃,inflater默认在android.view和android.widget包中寻找目标)

Demo截图
Box.java(矩形框的实例类,用于保存多个MotionEvent数据)

public class Box {
    private PointF mOrigin;  // 原始坐标点(手指的初始位置)
    private PointF mCurrent; // 当前坐标点(手指的当前位置)
    public Box(PointF origin) {
        mOrigin = origin;
        mCurrent = origin;
    }
    public PointF getOrigin() {
        return mOrigin;
    }
    public void setOrigin(PointF origin) {
        mOrigin = origin;
    }
    public PointF getCurrent() {
        return mCurrent;
    }
    public void setCurrent(PointF current) {
        mCurrent = current;
    }
}

BoxDrawingView.java (这是一个简单视图,是View的直接子类,这里添加了两个构造方法,因为视图可从代码或者布局文件实例化。从布局文件实例化的视图可收到一个AttributeSet实例,该实例包含了XML布局文件中指定的XML属性,自定义view很多时候是需要定制属性的,当然此Demo没有这一步。本书推荐,即使不打算使用构造方法,按习惯也应添加。万一未来需要使用呢,是吧,算是一种编程规范吧)

public class BoxDrawingView extends View {
    private static final String TAG = "BoxDrawingView";
    private Box mCurrentBox;
    private List<Box> mBoxes = new ArrayList<>();
    private Paint mBoxPaint;
    private Paint mBackgroundPaint;
    public BoxDrawingView(Context context) {
        this(context, null);
    }
    public BoxDrawingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mBoxPaint = new Paint();
        mBoxPaint.setColor(0x22ff0000);
        mBackgroundPaint = new Paint();
        mBackgroundPaint.setColor(0xfff8ef20);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        PointF current = new PointF(event.getX(), event.getY());
        String action = "";
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                action = "ACTION_DOWN";
                mCurrentBox = new Box(current);
                mBoxes.add(mCurrentBox);
                break;
            case MotionEvent.ACTION_MOVE:
                action = "ACTION_MOVE";
                if (mCurrentBox != null) {
                    mCurrentBox.setCurrent(current);
                    invalidate();   // 强制重绘,这样用户在屏幕上拖拽就能实时看到矩形框
                }
                break;
            case MotionEvent.ACTION_UP:
                action = "ACTION_UP";
                mCurrentBox = null;
                break;
            case MotionEvent.ACTION_CANCEL:
                action = "ACTION_CANCEL";
                mCurrentBox = null;
                break;
        }
        Log.i(TAG, action + " at x = " + current.x + ", y = " + current.y);
        return true;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawPaint(mBackgroundPaint);
        for (Box box : mBoxes) {
            float left = Math.min(box.getOrigin().x, box.getCurrent().x);
            float right = Math.max(box.getOrigin().x, box.getCurrent().x);
            float top = Math.min(box.getOrigin().y, box.getCurrent().y);
            float bottom = Math.max(box.getOrigin().y, box.getCurrent().y);
            canvas.drawRect(left, top, right, bottom, mBoxPaint);
        }
    }
}

监听触摸事件:

  • 方法一 —— 设置一个触摸事件监听器:
    public void setOnTouchListener(View.OnTouchListener l),工作方式与setOnClickListener(View.OnClickListener)相同,实现View.OnTouchListener接口,供触摸时间发生时调用。
  • 方法二 —— 这个Demo是在定制视图View的子类,捷径就是覆盖view的方法:public boolean onTouchEvent(MotionEvent event)
    该方法接受一个MotionEvent类实例,用于描述包括位置和动作的触摸事件。
    ACTION_DOWN------手指触摸到屏幕
    ACTION_MOVE------手指在屏幕上移动
    ACTION_UP------手指离开屏幕
    ACTION_CANCEL------父视图拦截了触摸事件
    BoxDrawingView.java中,X和Y坐标已经封装到了PointF对象中

两大绘制类:

  • Canvas:拥有我们需要的所有绘制操作,可决定在哪里以及绘什么,比如线条、圆形、字词、矩形等
  • Paint:决定如何绘制,可指定绘制图形的特征,例如是否填充图形、使用什么字体绘制、线条是什么颜色等。
    此章的Demo还是很简单的,大致就是了解下制定视图的基础,也没详细的描述自定义view原理

挑战练习:设备旋转问题

当设备旋转后,上面绘制的矩形框会消失,提示使用View方法:
protected Parcelable onSaveInstancesState()

protected void onRestoreInstanceState()

先贴我的方案代码吧:
BoxDrawingView.java

private static final String INDEX_BOX = "box";
private static final String INDEX_SUPER_STATE = "superState";
重写两个方法:
 @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable(INDEX_SUPER_STATE, super.onSaveInstanceState());
        bundle.putSerializable(INDEX_BOX, mBoxes);
        return bundle;
    }
    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if(state instanceof Bundle){
            Bundle bundle = (Bundle) state;
            this.mBoxes = (ArrayList<Box>) bundle.getSerializable(INDEX_BOX);
            state = bundle.getParcelable(INDEX_SUPER_STATE);
        }
        super.onRestoreInstanceState(state);
    }

当然还有注意:将Box实体类实现Serializable接口,原来定义mBoxes是private List<Box> mBoxes = new ArrayList<>();现在改为private ArrayList<Box> mBoxes = new ArrayList<>();因为ArrayList有实现Serializable接口,恰可以放入bundle中存储。还有一点,到布局文件中,给我们定制的view控件加个id属性。于是就可以完成第一个挑战练习了。

挑战总结:

  • 重写的两个方法不同于Activity和Fragment的onSaveInstanceState(Bundle)方法。View视图有ID时,才可以调用
  • 推荐使用Bundle,这样就不用自己实现Parcelable接口了(此接口实现起来比较复杂,尽量避免)
  • 需保存BoxDrawingView的View父视图的状态,在Bundle中保存super.onSaveInstanceState()方法结果,然后调用super.onRestoreInstanceState(Parcelable)方法把结果发送给超类。
    一开始,我就少了第三点,于是在旋转过程中一直奔溃,报错为
    Demo截图
    这里也就少了 bundle.putSerializable(INDEX_BOX, mBoxes);
    state = bundle.getParcelable(INDEX_SUPER_STATE);两句代码而已,当然别忘记了给控件加个id。

挑战练习:旋转矩形框

第三版将要补充的练习题,再来吧...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容