转载请说明出处:http://blog.csdn.net/ff20081528/article/details/17353869
一、准备知识
1、视图坐标与布局坐标的区别如下图所示:
上图是一个坐标系,这个坐标系是无边无际的。这个无边无际的坐标系即视图坐标。手机屏幕可视范围内的坐标即手机屏幕的布局坐标(坐标原点是屏幕的左上方的(0,0)位置)即A点。屏幕里面的子视图里面可视范围内的坐标即子视图的布局坐标(坐标原点是子视图的左上方的(0,0)位置)即B点。
2、android中布局关系
二、例子说明事件分发过程
这里我写了一个布局文件,展示效果如上图。当我点击View1,事件的分发过程是这样的:
1、ViewGroup3的dispatchTouchEvent()方法会被调用。
2、ViewGroup3调用ViewGroup2的dispatchTouchEvent()方法。
3、ViewGroup2调用ViewGroup1的dispatchTouchEvent()方法。
4、ViewGroup1会调用View1的dispatchTouchEvent()方法。
5、View1的dispatchTouchEvent()方法调用自己的onTouchEvent()方法。在onTouchEvent方法中处理点击事件。处理完了后会返回一个true给调用它的dispatchTouchEvent()方法。
6、ViewGroup1的dispatchTouchEvent()方法会返回一个true值给ViewGroup2的dispatchTouchEvent()方法。这样一直将则个true值返回到ViewGroup3的dispatchTouchEvent()方法。ViewGroup3在将这个值返回给调用它的方法。这样一个事件分发过程结束。
转载请说明出处:http://blog.csdn.net/ff20081528/article/details/17353869
三、ViewGroup中处理消息的详细过程
通过上面的列子对事件分发的过程有个大概的了解之后,我们通过ViewGroup中的dispatchTouchEvent()方法源码和View中的dispatchTouchEvent()方法及touchEvent方法源码来详细了解android是怎样处理这个过程的。
ViewGroup中的dispatchTouchEvent()方法源码如下:
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (!onFilterTouchEventForSecurity(ev)) {
- return false;
- }
- final int action = ev.getAction();
- final float xf = ev.getX();
- final float yf = ev.getY();
- final float scrolledXFloat = xf + mScrollX;
- final float scrolledYFloat = yf + mScrollY;
- final Rect frame = mTempRect;
- boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- if (action == MotionEvent.ACTION_DOWN) {
- if (mMotionTarget != null) {
- // this is weird, we got a pen down, but we thought it was
- // already down!
- // XXX: We should probably send an ACTION_UP to the current
- // target.
- mMotionTarget = null;
- }
- // If we're disallowing intercept or if we're allowing and we didn't
- // intercept
- if (disallowIntercept || !onInterceptTouchEvent(ev)) {
- // reset this event's action (just to protect ourselves)
- ev.setAction(MotionEvent.ACTION_DOWN);
- // We know we want to dispatch the event down, find a child
- // who can handle it, start with the front-most child.
- final int scrolledXInt = (int) scrolledXFloat;
- final int scrolledYInt = (int) scrolledYFloat;
- final View[] children = mChildren;
- final int count = mChildrenCount;
- for (int i = count - 1; i >= 0; i--) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- || child.getAnimation() != null) {
- child.getHitRect(frame);
- if (frame.contains(scrolledXInt, scrolledYInt)) {
- // offset the event to the view's coordinate system
- final float xc = scrolledXFloat - child.mLeft;
- final float yc = scrolledYFloat - child.mTop;
- ev.setLocation(xc, yc);
- child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- if (child.dispatchTouchEvent(ev)) {
- // Event handled, we have a target now.
- mMotionTarget = child;
- return true;
- }
- // The event didn't get handled, try the next view.
- // Don't reset the event's location, it's not
- // necessary here.
- }
- }
- }
- }
- }
- boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
- (action == MotionEvent.ACTION_CANCEL);
- if (isUpOrCancel) {
- // Note, we've already copied the previous state to our local
- // variable, so this takes effect on the next event
- mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
- }
- // The event wasn't an ACTION_DOWN, dispatch it to our target if
- // we have one.
- final View target = mMotionTarget;
- if (target == null) {
- // We don't have a target, this means we're handling the
- // event as a regular view.
- ev.setLocation(xf, yf);
- if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- }
- return super.dispatchTouchEvent(ev);
- }
- // if have a target, see if we're allowed to and want to intercept its
- // events
- if (!disallowIntercept && onInterceptTouchEvent(ev)) {
- final float xc = scrolledXFloat - (float) target.mLeft;
- final float yc = scrolledYFloat - (float) target.mTop;
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- ev.setAction(MotionEvent.ACTION_CANCEL);
- ev.setLocation(xc, yc);
- if (!target.dispatchTouchEvent(ev)) {
- // target didn't handle ACTION_CANCEL. not much we can do
- // but they should have.
- }
- // clear the target
- mMotionTarget = null;
- // Don't dispatch this event to our own view, because we already
- // saw it when intercepting; we just want to give the following
- // event to the normal onTouchEvent().
- return true;
- }
- if (isUpOrCancel) {
- mMotionTarget = null;
- }
- // finally offset the event to the target's coordinate system and
- // dispatch the event.
- final float xc = scrolledXFloat - (float) target.mLeft;
- final float yc = scrolledYFloat - (float) target.mTop;
- ev.setLocation(xc, yc);
- if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- mMotionTarget = null;
- }
- return target.dispatchTouchEvent(ev);
- }
代码说明:
1、(代码 2-4行)处理窗口处于模糊显示状态下的消息。所谓的模糊显示是指,应用程序可以设置当前窗口为模糊状态,此时窗口内部的所有视图将显示为模糊效果。这样 的目的是为了隐藏窗口中的内容,对于其中的各个视图而言,可以设置改视图的FILTER_TOUCHERS_WHEN_OBSCURED标识,如存在该标 识,则意味着用户希望不要处理该消息。
2、(代码6-11行)将ViewGroup的布局坐标转换成视图坐标。
3、(代码16-58行)处理ACTION_DOWN消息,其作用是判断该视图坐标落到了哪个子视图中。首先判断该ViewGroup本身是否被禁止获取 TOUCH消息,如果没有禁止,并且回调函数onInterceptTouchEvent中没有消耗该消息,则开始寻找子视图。调用 child.getHitRect(frame)方法获取该子视图在父视图中的布局坐标,即ViewGroup将child放在什么位置,这个位置相对于 该child来讲是布局坐标,而对于该ViewGroup来讲却是视图坐标,参数frame是执行完毕后的位置输出矩形。得到位置后,就可以调用 frame.contain()方法判断该消息位置是否被包含到了该child中,如果包含,并且该child也是一个ViewGroup,则准备递归调 用该child的dispatchTouchEvent(),在调用之前,首先需要把坐标重新转换到child的坐标系中。接下来判断该child是否是ViewGroup类。如果是,则诋毁调用ViewGroup的dispatchTouchEvent(),重新从第一步开始执行;如果child不是ViewGroup,而是一个View,则意味着诋毁调用的结束。
4、(代码61-68行)如果是ACTION_UP或者是ACTION_CANCEL消息,则清除mGroupFlags中的FLAG_DISALLOW_INTERCEPT标识,即允许该ViewGroup截获消息。也就是说,常见的情况就是当用户释放手指,下一次按下时,该ViewGroup本身可以重新截获消息,而在按下还没有释放期间,ViewGroup本身是不允许截获消息的。
5、(代码70-82)判断target变量是否为空。空代表了所有子窗口都没有消耗该消息,所以该ViewGroup本事需要处理该消息。然后还原消息的原始位置,将视图坐标重新转换成布局坐标,接着调用super.dispatchTouchEvent(ev),即View类的中的dispatchTouchEvent()方法(该方法会判断是否进行调用touchEvent()方法来处理,到后面的View类的源码中再介绍)。
6、(代码84-121)处理target存在,并且变量disallowIntercept为false,即允许截获,在默认情况下ViewGroup都是允许截获消息的,只有当该ViewGroup的子视图调用父视图的requestDisallowdInterceptTouchEvent()方法时,方可禁止父视图再次截获消息,但每次ACTION_UP消息或者ACTION_CANCEL消息之后,该ViewGroup又 会重新截获消息。ViewGroup本身不允许截获消息或者允许截获但是却没有消耗消息,于是调用 target.dispatchTouchEvent(ev)方法把该消息继续交给目标视图处理,在调用该方法前需要检查target中是否申明过要取消 随后的消息,即mPrivateFlags中包含 CANCEL_NEXT_UP_EVENT,如果是,则把消息action值修改为ACTION_CANCEL,将mMotionTarget变为空,因 为target不想处理接下来的消息了,那么就可以认为没有target了。
四、View中处理消息的详细过程
dispatchTouchEvent()方法的源码
- /**
- * Pass the touch screen motion event down to the target view, or this
- * view if it is the target.
- *
- * @param event The motion event to be dispatched.
- * @return True if the event was handled by the view, false otherwise.
- */
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (!onFilterTouchEventForSecurity(event)) {
- return false;
- }
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
- return true;
- }
- return onTouchEvent(event);
- }
处理窗口处于模糊显示状态下的消息。然后回调视图监听着的onTouch()方法,如果监听者消耗了该消息,则直接返回。如果没有则调用View的onTouchEvent()方法。
onTouchEvent()方法源码
- /**
- * Implement this method to handle touch screen motion events.
- *
- * @param event The motion event.
- * @return True if the event was handled, false otherwise.
- */
- public boolean onTouchEvent(MotionEvent event) {
- final int viewFlags = mViewFlags;
- if ((viewFlags & ENABLED_MASK) == DISABLED) {
- // A disabled view that is clickable still consumes the touch
- // events, it just doesn't respond to them.
- return (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
- }
- if (mTouchDelegate != null) {
- if (mTouchDelegate.onTouchEvent(event)) {
- return true;
- }
- }
- if (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP:
- boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
- if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
- // take focus if we don't have it already and we should in
- // touch mode.
- boolean focusTaken = false;
- if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
- focusTaken = requestFocus();
- }
- if (!mHasPerformedLongPress) {
- // This is a tap, so remove the longpress check
- removeLongPressCallback();
- // Only perform take click actions if we were in the pressed state
- if (!focusTaken) {
- // Use a Runnable and post this rather than calling
- // performClick directly. This lets other visual state
- // of the view update before click actions start.
- if (mPerformClick == null) {
- mPerformClick = new PerformClick();
- }
- if (!post(mPerformClick)) {
- performClick();
- }
- }
- }
- if (mUnsetPressedState == null) {
- mUnsetPressedState = new UnsetPressedState();
- }
- if (prepressed) {
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- postDelayed(mUnsetPressedState,
- ViewConfiguration.getPressedStateDuration());
- } else if (!post(mUnsetPressedState)) {
- // If the post failed, unpress right now
- mUnsetPressedState.run();
- }
- removeTapCallback();
- }
- break;
- case MotionEvent.ACTION_DOWN:
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- mPrivateFlags |= PREPRESSED;
- mHasPerformedLongPress = false;
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- break;
- case MotionEvent.ACTION_CANCEL:
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
- removeTapCallback();
- break;
- case MotionEvent.ACTION_MOVE:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- // Be lenient about moving outside of buttons
- int slop = mTouchSlop;
- if ((x < 0 - slop) || (x >= getWidth() + slop) ||
- (y < 0 - slop) || (y >= getHeight() + slop)) {
- // Outside button
- removeTapCallback();
- if ((mPrivateFlags & PRESSED) != 0) {
- // Remove any future long press/tap checks
- removeLongPressCallback();
- // Need to switch from pressed to not pressed
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
- }
- }
- break;
- }
- return true;
- }
- return false;
- }
- <p> </p>
1、代码10-15)处理该视图是否为disable状态,如果是,什么都不处理,返回true,即消耗该消息。
2、(代码17-21)消息代理处理消息。所谓的消息代理是指,可以给某个View指定一个消息处理代理,当View收到消息时,首先将该消息派发给其代理进行处理。如果代理内部消耗了该消息,则View不需要在进行任何处理;如果代理没有处理,则view继续处理。在这里不用多管,不会影响事件处理的逻辑和结果。
3、(代码22-102)判断该视图是否可以点击,如果不可以点击,则直接返回false,即不处理该消息。否则,真正开始执行触摸消息的默认处理逻辑,该逻辑分别处理了ACTION_DOWN、ACTION_MOVE、ACTION_UP消息.
(26-70)处理ACTION_UP消息,判断该UP消息是否发生在前面所讲的哪一个监测时间段中,并据此进行不同的处理。
(71-78)在ACTION_DOWN消息中,给mPrivateFlags变量添加PRESSED标识,并将变量mHasPerformLongPress设置为false,然后启动tap监测,即发送一个异步延迟消息。
对于触摸消息而言,消息本身没有repeat的属性,一次触摸只有一个ACTION_DOWN消息,按下来就是连续的ACTION_MOVE消息,并最终以处理ACTION_CANCEL消息.
(80-85)ACTION_CANCLE消息处理,这里只需清除PRESSED标识,刷新视图状态,然后再关闭tap监测即可。
(86-105)对ACTION_MOVE消息进行处理。具体逻辑包括,判断是否移动到了视图区域以外,如果是,则删除tap或者 longPress的监测,并清除mPrivateFlags中的PRESSED标识,然后调用refreshDrawableState()刷新视图状 态,这将导致对背景状态进行重绘。
五、例子程序
通过代码可能完全理解事件的分发过程有点难,下面通过一个例子程序来巩固下事件分发的过程。
例子程序的UI如下:
布局如下:
- <org.sunday.main.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" >
- <org.sunday.main.MyLinearLayout
- android:id="@+id/ll2"
- android:layout_width="match_parent"
- android:layout_height="100dp"
- android:background="@drawable/ll_selector"
- android:orientation="vertical" >
- </org.sunday.main.MyLinearLayout>
- <org.sunday.main.MyLinearLayout
- android:id="@+id/ll1"
- android:layout_width="match_parent"
- android:layout_marginTop="20dp"
- android:layout_height="100dp"
- android:background="@drawable/ll_selector"
- android:orientation="vertical" >
- <org.sunday.main.MyLinearLayout
- android:layout_width="80dp"
- android:layout_height="50dp"
- android:layout_marginTop="20dp"
- android:clickable="true"
- android:background="@drawable/ll_selector2"
- android:orientation="vertical" >
- </org.sunday.main.MyLinearLayout>
- </org.sunday.main.MyLinearLayout>
- <org.sunday.main.MyLinearLayout
- android:id="@+id/ll3"
- android:layout_width="match_parent"
- android:layout_height="100dp"
- android:layout_marginTop="20dp"
- android:background="@drawable/ll_selector"
- android:orientation="vertical" >
- <org.sunday.main.MyButton
- android:layout_width="wrap_content"
- android:layout_height="60dp"
- android:layout_margin="20dp"
- android:background="@drawable/btn_selector"
- android:text="button1" />
- </org.sunday.main.MyLinearLayout>
- <org.sunday.main.MyLinearLayout
- android:id="@+id/ll4"
- android:layout_width="match_parent"
- android:layout_height="100dp"
- android:layout_marginTop="20dp"
- android:background="@drawable/ll_selector"
- android:orientation="vertical" >
- <org.sunday.main.MyButtonWithOutEvent
- android:layout_width="wrap_content"
- android:layout_height="60dp"
- android:layout_margin="20dp"
- android:background="@drawable/btn_selector"
- android:text="button2" />
- </org.sunday.main.MyLinearLayout>
- </org.sunday.main.MyLinearLayout>
MyLinearLayout.java
- package org.sunday.main;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.widget.LinearLayout;
- public class MyLinearLayout extends LinearLayout {
- private final static String TAG = "MyLinearLayout";
- public MyLinearLayout(Context context) {
- super(context);
- }
- public MyLinearLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.e(TAG, "dispatchTouchEvent()----------------" + this.toString());
- boolean isTrue = super.dispatchTouchEvent(ev);
- Log.e(TAG, "dispatchTouchEvent() isTrue = " + isTrue + " --------------- ;"+ this.toString());
- return isTrue;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(TAG, "onTouchEvent()----------------" + this.toString());
- boolean isTrue = super.onTouchEvent(event);
- Log.e(TAG, "onTouchEvent() isTrue = " + isTrue+ "----------------- ;"+ this.toString());
- return isTrue;
- }
- }
MyButton.java
- package org.sunday.main;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.widget.Button;
- public class MyButton extends Button {
- private final static String TAG = "MyButton";
- public MyButton(Context context) {
- super(context);
- }
- public MyButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- Log.e(TAG, "dispatchTouchEvent()----------------");
- boolean isTrue = super.dispatchTouchEvent(event);
- Log.e(TAG, "dispatchTouchEvent isTrue = " + isTrue);
- return isTrue;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(TAG, "onTouchEvent()----------------");
- boolean isTrue = super.onTouchEvent(event);
- Log.e(TAG, "onTouchEvent isTrue = " + isTrue);
- return isTrue;
- }
- }
MyButtonWithOutEvent.java
- package org.sunday.main;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.util.Log;
- import android.view.MotionEvent;
- import android.widget.Button;
- public class MyButtonWithOutEvent extends Button {
- private final static String TAG = "MyButtonWithOutEvent";
- public MyButtonWithOutEvent(Context context) {
- super(context);
- }
- public MyButtonWithOutEvent(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- Log.e(TAG, "dispatchTouchEvent()----------------"+ this.toString());
- // boolean isTrue = super.dispatchTouchEvent(event);
- // Log.e(TAG, "isTrue = " + isTrue);
- return false;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(TAG, "onTouchEvent()----------------");
- boolean isTrue = super.onTouchEvent(event);
- Log.e(TAG, "onTouchEvent isTrue = " + isTrue);
- return isTrue;
- }
- }
点击例子一,后台打印的日志如下:
解释:例子一的布局中只有两个ViewGroup子类对象,当点击例子一区域,根ViewGroup首先分发touch事件,因为它有child, 所以接着会将事件传递到它的孩子ViewGroup。孩子ViewGroup发现自己没有child了,所以它就得自己处理这个touch事件。所以会调 用OnTouchEvent()方法来处理这个事件。处理完之后会返回一个true,一直返回给根ViewGroup。
点击例子二的绿色区域,后台打印日志如下:
解释:例子二和例子一差不多,只不过多了一个ViewGroup而已。
点击例子三得黄色区域,后台打印日志如下:
解释:例子三相比于例子二只是将最上层的ViewGroup换成了View,原理一样。
点击例子四的黄色区域,打印日志如下:
解释:在这个程序中,对于MyLinearLayout和MyButton的dispachTouchEvent()和 onTouchEvent()发放的逻辑并没有进行任何的修改。而对于MyButtonWithOutEvent,在 dispatchTouchEvent()方法中,直接返回的false(即不进行事件的任何处理),那么事件传递到这里时,它将会把事件返回给调用它的 父ViewGroup进行处理,所以这里我们看到的是MyLinearLayout调用了onTouchEvent()。
点击例子三得button和点击例子四的button效果图如下:
例子三中是button响应了这个touch事件,而在例子四中则是LinearLayout响应了这个touch事件。
demo:点击打开链接
相关推荐
android-collapse-calendar-view,简约炫酷,可以在月视图与周视图之间切换的calendar控件,博客附件,效果请查看博客相对应项目。
android-smart-image-view
android-pdfview是一个Android类库,提供一个快速读取显示Android PDF文件的组件——PDFView,该组件支持手势、动画和缩放。并且基于Google的VuDroid类库解码PDF文件。可以通过手势滑动PDF文件、对PDF文件进行放大...
sample-of-Android-week-view This is a sample of android week view,Thanks the author of android week view ,The WeekHeaderView is based on android week view .This project contains two view ,the header ...
Android-Timeline-View Android timeline to display horizontal sliding cards in recycler view, group by Day, Month or Year. Demo Video Apps using the library: Gourmet (http://thegourmet.app) Play...
自动播放pdf文件。源码为android-pdfview,github源码下载地址为:https://github.com/JoanZapata/android-pdfview,本压缩包中已经下载。在android-pdfview基础上,修改为自动翻页播放。
Android View 事件传递与消费,用于view事件传递的理解。
eclipse用的android-pdfview,用来显示pdf文件
Android Week View is an android library to display calendars (week view or day view) within the app. It supports custom styling. Features Week view calendar Day view calendar Custom styling ...
github上的开源项目android-pdfview,集成后需要添加libvudroid.so依赖.亲测可以打开本地pdf文件在应用中进行浏览翻页,效果不错。
Android-react-native-swipe-list-view.zip,一个react本地listview组件,其中的行可以打开和关闭,安卓系统是谷歌在2008年设计和制造的。操作系统主要写在爪哇,C和C 的核心组件。它是在linux内核之上构建的,具有...
一个Android的图片查看的开源框架,是github上下载的,免费分享
基于android-target-tooltip修改的---新功能提示view(可自定义提示样式)
android各组件详解- View
Android-LunarView A Chinese lunar calendar view. Screenshot Usage Use in xml: and you can add OnDatePickListener: lunarView.setOnDatePickListener(new LunarView.OnDatePickListener() { @Override ...
1、android-support-v4.jar,android-support-v7-appcompat.jar,非常实用的jar文件,不可缺少,兼容Android 对应版本,包中包含类许多我们可以调用的,如widget、view、util等等。 2、导入Unity或者Android Studio...
详细讲解Android View的工作原理。分析源码,讲解View的工作流程,何时开始触发等等。
Android Draw一个Android绘画View视图
android-segmented-control-view.zip,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
Android-Week-View-master