Android事件分发之View事件处理关键及示例分析

目录
  • 目的
  • View处理事件的关键
  • View事件处理分析
  • View.onTouchEvent()分析
    • 处理长按事件
    • 处理点击事件
    • 处理tap事件
  • 总结

目的

网上已经有很多关于事件分发的优秀文章,为何我还要自己写?因为别人总结的毕竟都是别人的,自己亲自阅读源码不仅会让自己更懂得原理,也会让自己记得更清楚,而且也会发现另一番天地。

View处理事件的关键

由于所以的控件都直接或者间接继承自View,因此View的事件分发机制就是最基础的一环,需要首先掌握其原理。

那么View的事件从哪里来的呢?当然是父View(一个ViewGroup)。父View在寻找能处理事件的子View的时候,会调用子View的dispatchTouchEvent()把事件传递给子View,如果子View的dispatchTouchEvent()返回true,代表子View处理了该事件,如果返回flase就代表该子View不处理事件。如果所有子View都不处理该事件,那么就由父View自己处理。

今天我们这篇文章就是来分析View如何处理事件。我们重点关心View.dispatchTouchEvent()啥时候返回true(代表处理了事件),啥时候返回false(代表不处理事件)。

View事件处理分析

    /**
     * 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) {
        boolean result = false;
        final int actionMasked = event.getActionMasked();
        // 当窗口被遮挡,是否过滤掉这个触摸事件
        if (onFilterTouchEventForSecurity(event)) {
            ListenerInfo li = mListenerInfo;
            // 1. 外部监听器处理
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            // 2. 自己处理
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        return result;
    }

可以看到View.dispatchTouchEvent()处理事件非常简单,要么交给外部处理,要么自己来处理。只要任何一方处理了,也就相应的处理函数返回trueView.dispatchTouchEvent()就返回true,代表View处理了事件。否则,View.dispatchTouchEvent()返回false,也就是View不处理该事件。

首先它把事件交给外部进行处理。外部处理指的什么呢?它指的就是交给setOnTouchListener()设置的监听器来处理。如果这个监听器处理时返回true,也就是OnTouchListener.onTouch()方法返回trueView.dispatchTouchEvent()就返回true,也就说明View处理了该事件。否则交给自己来处理,也就是交由onTouchEvent()处理。

当然,如果要让事件监听器来处理,还必须要让View处于enabled状态。可以通过setEnabled()方法来改变View的enabled状态。并且可以通过isEnabled()查询View是否处于enabled状态。

当外部无法处理时,也就是上面的三个条件有一个不满足时,就交给View.onTouchEvent()来处理。此时View.onTouchEvent()的返回值就决定了View.dispatchTouchEvent()的返回值。也就是决定了View是否处理该事件。那么,我们来看下View.onTouchEvent()什么时候返回true,什么时候返回false

    public boolean onTouchEvent(MotionEvent event) {
        // 判断View是否可点击(点击/长按)
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        // 处理View是disabled状态的情况
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // ...
            return clickable;
        }
        // 如果有处理代表,就先交给它处理。如果它不处理,就继续交给自己处理
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        // 如果可以点击,最后会返回true,代表处理了View处理了事件
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            // ...
            return true;
        }
        return false;
    }

这太让人意外了,只要View可以点击(点击/长按),就返回true,否则返回false

忽略触摸代理(Touch Delegate)和CONTEXT_CLICKABLE的特性,因为不常用,如果遇到了,可以再来查看。

那么,View默认可以点击,长按吗?当然是不能。这需要子View自己去设置,例如Button在构造函数中就设置了自己可以点击。

我们从代码角度解释下View默认是否可以点击和长按

    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
                // View构造函数中解析android:clickable属性
                case com.android.internal.R.styleable.View_clickable:
                    // 第二个参数false,表示View默认不可点击
                    if (a.getBoolean(attr, false)) {
                        viewFlagValues |= CLICKABLE;
                        viewFlagMasks |= CLICKABLE;
                    }
                    break;
                // View构造函数中解析android:longClickable属性
                case com.android.internal.R.styleable.View_longClickable:
                    // 第二个参数false,表示View默认不可长按
                    if (a.getBoolean(attr, false)) {
                        viewFlagValues |= LONG_CLICKABLE;
                        viewFlagMasks |= LONG_CLICKABLE;
                    }
                    break;
    }

在View的构造函数中分别接下了android:clickableandroid:longClickable属性,从默认值可以看出,View默认是不可点击和长按的。也就是说View默认不处理任何事件。

那么,我们用一张图来解释View如何处理触摸事件的

通过这张图,我们就可以清楚的了解到View.dispatchTouchEvent()在什么情况下返回 true,在什么情况下返回false。也就了解了View在什么情况下处理了事件,在什么情况下不处理事件。

View.onTouchEvent()分析

View事件处理就这么简单吗?如果你只关心事件分发到哪里,以及谁处理了事件,那么掌握上面的流程就够了。

但是你是否还有个疑问,View.onTouchEvent()在干啥呢?OK,如果你保持这份好奇心,那么接着往下看。

View.onTouchEvent()其实处理了三种情况

  • 处理点击事件
  • 处理长按事件
  • 处理View状态改变
  • 处理tap事件

处理长按事件

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    // 1. 判断是否在一个滚动的容器中
                    boolean isInScrollingContainer = isInScrollingContainer();
                    if (isInScrollingContainer) {
                        // 1.1 在滚动容器中
                        // ...
                    } else {
                        // 1.2 不是在滚动容器中
                        // 设置按下状态
                        setPressed(true, x, y);
                        // 检测长按动作
                        checkForLongClick(0, x, y);
                    }
                    break;

setPressed()方法首先会设置View为按下状态, 代码如下

 mPrivateFlags |= PFLAG_PRESSED;

然后,通过checkForLongClick()来检测长按动作,这是如何实现呢

    private void checkForLongClick(int delayOffset, float x, float y) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;
            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            // 在长按超时的时间点,执行一个Runable,也就是CheckForLongPres
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }
    private final class CheckForLongPress implements Runnable {
        @Override
        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                // 执行长按动作
                if (performLongClick(mX, mY)) {
                    // 如果处理了长按动作,mHasPerformedLongPress为true
                    mHasPerformedLongPress = true;
                }
            }
        }
    }

其实它是把CheckForLongPress这个Runnable加入到Message Queue中,然后在ViewConfiguration.getLongPressTimeout()这个长按超时的时间点执行。

这是什么意思呢?首先在ACTION_DOWN的时候我检测到按下的动作,那么在还没有执行ACTION_UP之前,如果按下动作超时了,也就是超过了长按的时间点,那么我会执行长按动作performLongClick()。我们现在看下执行长按做了哪些事情

    public boolean performLongClick(float x, float y) {
        // 记录长按的位置
        mLongClickX = x;
        mLongClickY = y;
        // 执行长按的动作
        final boolean handled = performLongClick();
        // 重置数据
        mLongClickX = Float.NaN;
        mLongClickY = Float.NaN;
        return handled;
    }
    public boolean performLongClick() {
        return performLongClickInternal(mLongClickX, mLongClickY);
    }
    private boolean performLongClickInternal(float x, float y) {
        boolean handled = false;
        final ListenerInfo li = mListenerInfo;
        // 1. 执行长按监听器处理动作
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
        // 2. 如果长按监听器不处理,就显示上下文菜单
        if (!handled) {
            final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
            handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
        }
        // 3. 如果处理了长按事件,就执行触摸反馈
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    }

有三个动作在长按的时候执行

  • 如果调用过setOnLongClickListener()给View设置长按事件监听器,那么首先把长按事件交给这个监听器处理。如果这个监听器返回true,代表监听器已经处理了长按事件,那么直接执行第三步的触摸反馈,并返回。如果这个监听器返回了false,代表监听没有处理长按事件,那么就执行第二步,交给系统处理。
  • 当第一步处理不了时,系统自己来处理,它会显示一个上下文菜单。
  • 执行触摸反馈。

如果你不了解什么是上下文菜单(Context Menu)和触摸反馈(Haptic Feednack),可以自行搜索下。

我们已经了解了如果触发长按做了哪些动作,但是我们也要记得触发长按的时机,那就是从手指按下到抬起的时间要超过长按的超时时间。如果没有超过这个长按超时时间,在ACTION_UP的时候,系统会怎么做呢?

                case MotionEvent.ACTION_UP:
                    // 处于按下状态
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 没有执行长按动作
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除长按动作
                            removeLongPressCallback();
                        }
                    }
                    break;

当检测到ACTION_UP时,如果见到了View处于按下状态,但是还没有执行长按动作。也就是说,还没有达到长按的时间点,手指就抬起了,那么系统就会移除在ACTION_DOWN添加的长按动作,之后长按动作就不会触发了。

处理点击事件

我们先分析了长按事件而没有分析点击事件,其实是为了更好的讲清楚点击事件,看代码

                case MotionEvent.ACTION_DOWN:
                    if (isInScrollingContainer) {
                    } else {
                        // 设置按下状态
                        setPressed(true, x, y);
                    }
                    break;

当检测到ACTION_DOWN事件,首先的给它设置一个按下标记,这个前面说过。然后在没有达到长按超时这个时间点前,如果检测到ACTION_UP事件,那么我们就可以认为这是一次点击事件

                case MotionEvent.ACTION_UP:
                    // 处于按下状态
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 没有执行长按
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除长按动作
                            removeLongPressCallback();
                            // focusTaken是在touch mode下有效,现在讨论的是简单的手指触摸
                            if (!focusTaken) {
                                // 创建点击事件
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                // 通过Message Queue执行点击事件
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                    }
                    break;

Touch Mode模式与D-pad有关,读者可以查阅官方文档说明。

有了前面关于长按事件的知识,这里就非常好理解了。

如果没有执行长按动作,就先移除长按回调,那么以后就不会再执行长按动作了。相反,如果已经执行长按动作,那么就不会执行点击事件。

performClick()用来执行点击事件,那么来看下它做了什么

    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            // 1. 首先交给外部的点击监听器处理
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            // 2. 如果没有外部监听器,就不处理了
            result = false;
        }
        return result;
    }

点击事件的处理非常简单粗暴,默认就不处理,也就是返回false。当然,如果你想处理,调用setOnClickListener()即可。

###处理View状态改变

响应View状态改变的操作都集中在setPressed()方法中,其实我们再进一步思考下,View只对按下和抬起的状态进行响应

    public void setPressed(boolean pressed) {
        // 1. 判断是否需要执行刷新动作
        final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
        // 2. 设置状态
        if (pressed) {
            // 设置按下状态
            mPrivateFlags |= PFLAG_PRESSED;
        } else {
            // 取消按下状态
            mPrivateFlags &= ~PFLAG_PRESSED;
        }
        // 3. 如果需要刷新就刷新View管理的Drawable状态
        if (needsRefresh) {
            refreshDrawableState();
        }
        // 4. 如果是ViewGroup,就需要把这个状态分发给子View
        dispatchSetPressed(pressed);
    }

如果手指按下了,会调用setPressed(true),如果手指抬起了,会调用setPressed(false)

假设我们手指刚按下,那么就需要执行第三步的刷新Drawable状态的动作

    public void refreshDrawableState() {
        // 标记Drawable状态需要刷新
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        // 执行Drawable状态改变的动作
        drawableStateChanged();
        // 通知父View,子View的Drawable状态改变了
        ViewParent parent = mParent;
        if (parent != null) {
            parent.childDrawableStateChanged(this);
        }
    }

首先设置PFLAG_DRAWABLE_STATE_DIRTY标记,表示Drawable状态需要更新,然后调用drawableStateChange()来执行Drawable状态改变动作

    @CallSuper
    protected void drawableStateChanged() {
        // 1. 获取Drawable新状态
        final int[] state = getDrawableState();
        boolean changed = false;
        // 2. 为View管理的各种Drawable设置新状态
        final Drawable bg = mBackground;
        if (bg != null && bg.isStateful()) {
            changed |= bg.setState(state);
        }
        final Drawable fg = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (fg != null && fg.isStateful()) {
            changed |= fg.setState(state);
        }
        if (mScrollCache != null) {
            final Drawable scrollBar = mScrollCache.scrollBar;
            if (scrollBar != null && scrollBar.isStateful()) {
                changed |= scrollBar.setState(state)
                        && mScrollCache.state != ScrollabilityCache.OFF;
            }
        }
        // 3. 为StateListAnimator设置新状态,从而改变Drawable
        if (mStateListAnimator != null) {
            mStateListAnimator.setState(state);
        }
        // 4. 如果有Drawable状态更新了,就重绘
        if (changed) {
            invalidate();
        }
    }

既然我们要给Drawable更新状态,那么就的获取新的状态值,这就是第一步所做的事情,我们来看下getDrawableState()如何获取新状态的

    public final int[] getDrawableState() {
        // 如果Drawable状态没有改变,就直接返回之前的状态值
        if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
            return mDrawableState;
        }
        // 如果状态值不存在,或者Drawable状态需要更新
        else {
            // 创建状态值
            mDrawableState = onCreateDrawableState(0);
            // 重置PFLAG_DRAWABLE_STATE_DIRTY状态
            mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
            return mDrawableState;
        }
    }

刚刚,我们设置了PFLAG_DRAWABLE_STATE_DIRTY,标志着Drawable状态需要更新,因此这里会调用onCreateDrawableState()来获取

    protected int[] onCreateDrawableState(int extraSpace) {
        // 默认是没有设置DUPLICATE_PARENT_STATE状态
        if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
                mParent instanceof View) {
            return ((View) mParent).onCreateDrawableState(extraSpace);
        }
        int[] drawableState;
        // 2. 根据各种flag, 获取状态
        int privateFlags = mPrivateFlags;
        int viewStateIndex = 0;
        if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
        if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
        if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
        if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
        if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
        if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
        if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
                ThreadedRenderer.isAvailable()) {
            // This is set if HW acceleration is requested, even if the current
            // process doesn't allow it.  This is just to allow app preview
            // windows to better match their app.
            viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;
        }
        if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED;
        final int privateFlags2 = mPrivateFlags2;
        if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {
            viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;
        }
        if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {
            viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;
        }
        // 2. 把状态值转化为一个数组
        drawableState = StateSet.get(viewStateIndex);
        if (extraSpace == 0) {
            return drawableState;
        }
    }

首先根据各种标志位,例如mPrivateFlagsmPrivateFlags2,来获取状态的值,然后根据状态的值获取一个状态的数组。

我想你一定想直到这个状态数组是咋样的,我举个例子,View默认是enabled状态,那么mViewFlags默认设置了ENABLED标记,当我们手指按下的时候,mPrivateFlags设置了PFLAG_PRESSED按下状态标记。如果值选择这两个情况来获取状态值,那么viewStateIndex = VIEW_STATE_PRESSED | VIEW_STATE_ENABLED,用二进制表示就是11000。然后通过StateSet.get(viewStateIndex)转化为数组就是[StateSet.VIEW_STATE_ENABLED, StateSet.VIEW_STATE_PRESSED]

现在,我们获取到Drawable新的状态值,那么就可以进行drawableStateChanged()函数的第二步,为各种Drawable设置新的状态值,例如背景Drawable,前景Drawable。这些Drawable根据这些新的状态值,自己判断是否需要更新Drawable,例如更新显示的大小,颜色等等。如果更新了Drawable,那么就会返回true,否则返回false

drawableStateChanged()函数的第三步,还针对了StateListAnimator的处理。StateListAnimator会根据View状态值,改变Drawable的显示。

如果大家不了解StateListAnimator,可以网上查阅下它的使用,这样就可以对View状态改变有更深层次的理解。

drawableStateChanged()函数的第四步,如果有任意Drawable改变了状态,那么就需要View进行重绘。

处理tap事件

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    // 1. 判断View是否在滚动容器中
                    boolean isInScrollingContainer = isInScrollingContainer();
                    if (isInScrollingContainer) {
                        // 标记要触发tab事件
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        // 2. 如果View在滚动容器中,那么检测一个tab动作
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                    }
                    break;

第一步,判断View是否在一个滚动的容器中

    public boolean isInScrollingContainer() {
        ViewParent p = getParent();
        while (p != null && p instanceof ViewGroup) {
            if (((ViewGroup) p).shouldDelayChildPressedState()) {
                return true;
            }
            p = p.getParent();
        }
        return false;
    }

通过循环遍历父View,并调用父View的shouldDelayChildPressedState()方法来判断父View是否是一个滚动容器。

那么什么样的ViewGroup是滚动容器呢?例如ScrollView就是一个滚动容器,因为它有让子View滚动的特性,所以shouldDelayChildPressedState()返回true。而LinearLayout就不是一个滚动容器,它本身没有设计滚动特性,因此shouldDelayChildPressedState()返回false

当View处于一个滚动容器中,并且容器处于滚动中,这个View需要检测一个tap事件,也就是表示快速点击。它有个触发的超时时间,大小为100ms(长按的触发超时时间是500ms),因此只要按下的事件超过100ms, 都算作一次tap事件。那么,我们先来看下触发tap事件都做了啥事

    private final class CheckForTap implements Runnable {
        public float x;
        public float y;
        @Override
        public void run() {
            // 先取消tab的标记
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            // 设置按下状态
            setPressed(true, x, y);
            // 检测长按事件
            checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
        }
    }

当触发了tap事件,首先取消标记,表示tap事件已经执行。然后,既然已经发生了点击事件,那么自然要设置按下状态。最后由于tap事件是在长按事件之前触发,那么当tap事件触发后,自然要去检测长按事件是否触发。

我们刚刚说到,tap事件触发的条件是,在滚动容器中,从手指按下到抬起的时间要过100ms。那么如果在100ms之前抬起了手指,那么会怎么处理呢,我们来看下ACTION_UP的处理逻辑

                case MotionEvent.ACTION_UP:
                    // 判断tap动作是否已经完成
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    // 如果是按下状态或者还没有触发tap动作
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 1. 如果还没有触发tap动作,就设置按下状态
                        if (prepressed) {
                            setPressed(true, x, y);
                       }
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除长按回调
                            removeLongPressCallback();
                            // 2. 执行点击事件
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                        // 3. 移除tap回调
                        removeTapCallback();
                    }
                    break;

prepressedtrue表示没有执行tap事件,那么当检测到手指抬起时,先设置按下状态。如果连tap都没执行,肯定也不会执行长按事件,因此只会执行点击事件。最后,移除长按回调,这样tap事件就不会再触发。

如果tap事件执行了呢?只有一点差别,将会在第二步,根据是否执行了长按来决定是否执行点击事件。

总结

通过本文的分析,我们可以清楚的知道View如何处理父View传递过来的事件,也可以清楚知道View在什么时候处理事件,什么时候不处理事件。

另外,本文也对View.onTouchEvent()作出分析,我们可以清楚知道View如何处理点击事件,如何处理长按事件,如何处理状态改变,以及如何处理tap事件。

以上就是Android事件分发之View事件处理关键及示例分析的详细内容,更多关于Android事件分发View事件处理的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android事件处理的两种方式详解

    安卓提供了两种方式的事件处理:基于回调的事件处理和基于监听的事件处理. 基于监听的事件处理 基于监听的事件处理一般包含三个要素,分别是: Event Source(事件源):事件发生的场所,通常是各个组件 Event(事件):事件封装了界面组件上发生的特定事件(通常就是用户的一次操作) Event Listener(事件监听器):负责监听事件源发生的事件,并对各种事件作出相应的响应 下面使用一个简单的案例介绍按钮事件监听器 布局文件就是简单的线性布局器,上面是一个EditText,下面是一个Bu

  • Android 中 EventBus 的使用之多线程事件处理

    在这一系列教程的最后一篇中,我想谈谈GR的EventBus,在处理多线程异步任务时是多么简单而有效. AsyncTask, Loader和Executor-- 拜托! Android中有很多种执行异步操作的方法(指平行于UI线程的).AsyncTask对于用户来说是最简单的一种机制,并且只需要少量的设置代码即可.然而,它的使用是有局限的,正如Android官方文档中所描述的: AsyncTask被设计成为一个工具类,在它内部包含了Thread和Handler,但它本身并不是通用线程框架的一部分.

  • Android的Touch事件处理机制介绍

    Android的Touch事件处理机制比较复杂,特别是在考虑了多点触摸以及事件拦截之后. Android的Touch事件处理分3个层面:Activity层,ViewGroup层,View层. 首先说一下Touch事件处理的几条基本规则. 如果在某个层级没有处理ACTION_DOWN事件,那么该层就再也收不到后续的Touch事件了直到下一次ACTION_DOWN事件. 说明: a.某个层级没有处理某个事件指的是它以及它的子View都没有处理该事件. b.这条规则不适用于Activity层(它是顶层

  • Android中父View和子view的点击事件处理问题探讨

    android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件,有必要对它进行深入的了解. 一个最简单的屏幕触摸动作触发了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE...->ACTION_MOVE->ACTION_UP 当屏幕中包含一个ViewGroup,而这个ViewGroup又包含一个子view,这个时候android系统如何处理Touch事件呢?到底是ViewG

  • Android开发多手指触控事件处理

    目录 正文 触摸事件 事件类型 手指索引 手指ID 多手指事件处理 支持多手指的滑动控件 总结 正文 多点触控,一直以来都是事件处理中比较晦涩的一个话题.其一是因为它的机制与我们常规思维有点不同,基二是因为我们用的比较少.那么作为一个有点追求的Android开发者,我们必须要掌握这些,这样可以提高代码的格调. 写这篇文章还是有点难度的,我反反复复修改了好多次,真的是删了又改,改了又删,只为把多点触控讲得明明白白.最后我决定把本文分为三部分进行讲解 讲解多手指触摸的一些关键性概念.虽然这部分概念非

  • Android XRecyclerView最简单的item点击事件处理

    以前一直都是用PullToRefresh,后来觉得还是太out了.现在很多人都是用RecyclerView,很简单的用法,布局多样化,主要是有瀑布流.这才知道RecyclerView.LayoutManager真正的强大. 但是说要addHeaderView这个的话,RecyclerView没有实现,所以我用了XRecyclerView,其实它也是在RecyclerView的基础上再次封装的,用起来还是蛮好的. 这里说一下,正确的使用XRecyclerView点击item做事件处理的问题.其实就

  • 详解Android的两种事件处理机制

    UI编程通常都会伴随事件处理,Android也不例外,它提供了两种方式的事件处理:基于回调的事件处理和基于监听器的事件处理. 对于基于监听器的事件处理而言,主要就是为Android界面组件绑定特定的事件监听器:对于基于回调的事件处理而言,主要做法是重写Android组件特定的回调函数,Android大部分界面组件都提供了事件响应的回调函数,我们主要重写它们就行. 一 基于监听器的事件处理 相比于基于回调的事件处理,这是更具"面向对象"性质的事件处理方式.在监听器模型中,主要涉及三类对象

  • Android开发事件处理的代码如何写手摸手教程

    目录 正文 剖析事件分发的过程 ACTION_DOWN ACTION_MOVE ACTION_UP ACTION_CANCEL 完成案例代码 ACTION_DOWN ACTION_MOVE ACTION_UP ACTION_CANCEL 截断ACTION_DOWN 结束 正文 经过事件分发之View事件处理和ViewGroup事件分发和处理源码分析这两篇的的理论知识分析,我们已经大致的了解了事件的分发处理机制,但是这并不代表你就一定能写好事件处理的代码. 既然我们有了基本功,那么本文就通过一个案

  • Android事件分发之View事件处理关键及示例分析

    目录 目的 View处理事件的关键 View事件处理分析 View.onTouchEvent()分析 处理长按事件 处理点击事件 处理tap事件 总结 目的 网上已经有很多关于事件分发的优秀文章,为何我还要自己写?因为别人总结的毕竟都是别人的,自己亲自阅读源码不仅会让自己更懂得原理,也会让自己记得更清楚,而且也会发现另一番天地. View处理事件的关键 由于所以的控件都直接或者间接继承自View,因此View的事件分发机制就是最基础的一环,需要首先掌握其原理. 那么View的事件从哪里来的呢?当

  • Android编程开发之Spinner控件用法实例分析

    本文实例讲述了Android编程开发之Spinner控件用法.分享给大家供大家参考,具体如下: 下拉列表 Spinner,Spinner是一个每次只能选择所有项的一个项的控件.它的项来自于与之相关联的适配器中. Spinner的使用,可以极大提高用户的体验性.当需要用户选择的时候,可以提供一个下拉列表将所有可选的项列出来.供用户选择. 一.使用数组作为数据源 布局文件: <RelativeLayout xmlns:android="http://schemas.android.com/ap

  • Android编程基于自定义view实现公章效果示例【附源码下载】

    本文实例讲述了Android编程基于自定义view实现公章效果.分享给大家供大家参考,具体如下: 上次去一个公司面试,面试官问了一个题,怎么用android的自定义view实现一个公章的效果,据说这是华为之前的面试题,我想了下,要是公章的效果,最外层是一个圆,里面是一个五角星,但是这文字怎么画呢,比较难搞,后来回来看了下java的api,发现人家的Path里面本来就提供了这么一个方法: public void addArc(RectF oval, float startAngle, float

  • android开发教程之view组件添加边框示例

    给TextureView添加边框(专业名词为描边),有三种解决方案: 1.设置一个9 patch 的,右边框,中间是空的PNG. 2.自定义一个View,用Canvas画个边框. 3.用Android提供的ShapeDrawable来定义一个边框. 个人比较建议采用第三种方式,原因是因为第三种只要写XML,速度快,占用资源小,代码编写量也少,便于维护. 使用方法如下: 1.定义一个background.xml文件. 复制代码 代码如下: <?xml version="1.0" e

  • Android Studio开发之 JNI 篇的简单示例

    前言 Android上层应用使用java开发,不过java并不适合密集型运算,比如图片处理等,遇到密集型运算,一般使用c/c++完成. Java虚拟机支持调用c/c++代码,即JNI(Java Native Interface),它提供了若干的API实现了Java和其他语言的通信.为方便android平台上使用JNI技术,提供了NDK开发包,可以将NDK理解为对JNI的进一步封装,方便开发使用罢了. JNI开发方式有多种,可以在Android 源码中开发,也可以利用其它工具,但都比较烦琐或者要下

  • Android事件分发机制(下) View的事件处理

    综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

  • Android 深入探究自定义view之事件的分发机制与处理详解

    目录 题引 Activity对事件的分发过程 父布局拦截的分发处理过程 ACTION_DOWN 事件 ACTION_MOVE 事件 父布局不拦截时的分发处理过程 ACTION_DOWN ACTION_MOVE 解决冲突方案 外部拦截 内部拦截 本文主要探讨下面几个问题: 学习事件分发机制是为了解决什么问题 Activity对事件的分发过程 父布局拦截的分发处理过程 父布局不拦截时的分发处理过程 冲突解决方案 题引 事件只有一个,多个人想要处理,处理的对象不是我们想给的对象就是事件冲突. 如上图,

  • Android事件传递机制

    实验环境 OS X 10.9 Eclipse(ADT) Android源码版本:API Level 19(Android 4.4) Android事件构成 在Android中,事件主要包括点按.长按.拖拽.滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作.所有这些都构成了Android中的事件响应.总的来说,所有的事件都由如下三个部分作为基础: 按下(ACTION_DOWN) 移动(ACTION_MOVE) 抬起(ACTION_UP) 所有的操作事件首先必须执行的是按下操作(ACTIO

  • Android事件分发机制(上) ViewGroup的事件分发

    综述 Android中的事件分发机制也就是View与ViewGroup的对事件的分发与处理.在ViewGroup的内部包含了许多View,而ViewGroup继承自View,所以ViewGroup本身也是一个View.对于事件可以通过ViewGroup下发到它的子View并交由子View进行处理,而ViewGroup本身也能够对事件做出处理.下面就来详细分析一下ViewGroup对时间的分发处理. MotionEvent 当手指接触到屏幕以后,所产生的一系列的事件中,都是由以下三种事件类型组成.

  • Android程序开发之Listview下拉刷新上拉(滑动分页)加载更多

    最近做的类似于微博的项目中,有个Android功能要使用到listview的向下拉刷新来刷新最新消息,向上拉刷新(滑动分页)来加载更多. 新浪微博就是使用这种方式的典型. 当用户从网络上读取微博的时候,如果一下子全部加载用户未读的微博这将耗费比较长的时间,造成不好的用户体验,同时一屏的内容也不足以显示如此多的内容.这时候,我们就需要用到另一个功能,那就是listview的分页了,其实这个分页可以做成客户端的分页,也可以做成服务器端的分页(点击加载时,从服务器对应的加载第N页就好了!!!).通过分

随机推荐