GlowPadView

GlowPadView是通话界面中来电所用的控件,比较复杂。已有人写过Android 4.2 关于GlowPadView的说明,介绍了xml中的各种属性,并贴图详细解释,强烈推荐看本文前先看链接中的文章。

packages/apps/InCallUI/src/com/android/incallui/widget/multiwaveview下都是GlowPadView相关的文件

动画

Ease.java

static class Linear {
        public static final TimeInterpolator easeNone = new TimeInterpolator() {
            public float getInterpolation(float input) {
                return input;
            }
        };
    }

    static class Cubic {
        public static final TimeInterpolator easeIn = new TimeInterpolator() {
            public float getInterpolation(float input) {
                return DOMAIN*(input/=DURATION)*input*input + START;
            }
        };
        public static final TimeInterpolator easeOut = new TimeInterpolator() {
            public float getInterpolation(float input) {
                return DOMAIN*((input=input/DURATION-1)*input*input + 1) + START;
            }
        };
        public static final TimeInterpolator easeInOut = new TimeInterpolator() {
            public float getInterpolation(float input) {
                return ((input/=DURATION/2) < 1.0f) ?
                        (DOMAIN/2*input*input*input + START)
                            : (DOMAIN/2*((input-=2)*input*input + 2) + START);
            }
        };
    }
Ease中定义了多种Interpolator,控制动画执行变化率。

Tweener.java

主要用于存储多个动画

  ObjectAnimator animator;
    private static HashMap<Object, Tweener> sTweens = new HashMap<Object, Tweener>();
Tweener成员就是一个animator,并有一个静态HashMap存储多个Tweener,该类中最重要的是生成Tweener的to方法:

public static Tweener to(Object object, long duration, Object... vars) { //Object为拥有属性值的对象,duration是时长
        long delay = 0;
        AnimatorUpdateListener updateListener = null;
        AnimatorListener listener = null;
        TimeInterpolator interpolator = null;

        // Iterate through arguments and discover properties to animate
        ArrayList<PropertyValuesHolder> props = new ArrayList<PropertyValuesHolder>(vars.length/2);
        for (int i = 0; i < vars.length; i+=2) { //vars必须是双数,每一对是key和values,每一对都能生成动画的一部分
            if (!(vars[i] instanceof String)) {
                throw new IllegalArgumentException("Key must be a string: " + vars[i]);
            }
            String key = (String) vars[i];
            Object value = vars[i+1];

            if ("simultaneousTween".equals(key)) {
                // TODO
            } else if ("ease".equals(key)) {   //key为ease的话是设置interpolator 
                interpolator = (TimeInterpolator) value; // TODO: multiple interpolators?
            } else if ("onUpdate".equals(key) || "onUpdateListener".equals(key)) { 
                updateListener = (AnimatorUpdateListener) value;  //key为onUpdate是设置updateListener
            } else if ("onComplete".equals(key) || "onCompleteListener".equals(key)) {
                listener = (AnimatorListener) value; //key为onComplete是设置动画结束的listener
            } else if ("delay".equals(key)) {
                delay = ((Number) value).longValue(); //key为delay是设置动画延时
            } else if ("syncWith".equals(key)) {
                // TODO
            } else if (value instanceof float[]) {  //value为float数组则加入一个float动画属性
                props.add(PropertyValuesHolder.ofFloat(key,
                        ((float[])value)[0], ((float[])value)[1]));
            } else if (value instanceof int[]) { //value为int数组则加入一个int动画属性
                props.add(PropertyValuesHolder.ofInt(key,
                        ((int[])value)[0], ((int[])value)[1]));
            } else if (value instanceof Number) { value为数字包装类型,例如Integer等则统一按float动画属性处理
                float floatValue = ((Number)value).floatValue();
                props.add(PropertyValuesHolder.ofFloat(key, floatValue));
            } else {
                throw new IllegalArgumentException(
                        "Bad argument for key \"" + key + "\" with value " + value.getClass());
            }
        }

        // Re-use existing tween, if present
        Tweener tween = sTweens.get(object);
        ObjectAnimator anim = null;
        if (tween == null) { //HashMap中没有取到动画则新建Tweener
            anim = ObjectAnimator.ofPropertyValuesHolder(object,
                    props.toArray(new PropertyValuesHolder[props.size()]));
            tween = new Tweener(anim);
            sTweens.put(object, tween); //存储到HashMap中
            if (DEBUG) Log.v(TAG, "Added new Tweener " + tween);
        } else {
            anim = sTweens.get(object).animator;  //已有动画则替换动画
            replace(props, object); // Cancel all animators for given object
        }
 
        if (interpolator != null) { //后续都是设置动画listener、interpolator等
            anim.setInterpolator(interpolator); 
        }

        // Update animation with properties discovered in loop above
        anim.setStartDelay(delay);
        anim.setDuration(duration);
        if (updateListener != null) {
            anim.removeAllUpdateListeners(); // There should be only one
            anim.addUpdateListener(updateListener);
        }
        if (listener != null) {
            anim.removeAllListeners(); // There should be only one.
            anim.addListener(listener);
        }
        anim.addListener(mCleanupListener);

        return tween;
    }
该方法也是静态的,这样外界使用Tweener就是通过这一个方法。可以看出Tweener没有添加什么功能,唯一作用是Tweener生成动画的代码片段明显比手动生成一个动画简洁好多,尤其是GlowPadView中有很多用动画的地方,截取一个代码片段如下:

            mTargetAnimations.add(Tweener.to(target, duration,
                    "ease", interpolator,
                    "alpha", 0.0f,
                    "scaleX", targetScale,
                    "scaleY", targetScale,
                    "delay", delay,
                    "onUpdate", mUpdateListener));

AnimationBundle

 private class AnimationBundle extends ArrayList<Tweener> {
        private static final long serialVersionUID = 0xA84D78726F127468L;
        private boolean mSuspended;

        public void start() {
            if (mSuspended) return; // ignore attempts to start animations
            final int count = size();
            for (int i = 0; i < count; i++) {
                Tweener anim = get(i);
                anim.animator.start();
            }
        }

        public void cancel() {
            final int count = size();
            for (int i = 0; i < count; i++) {
                Tweener anim = get(i);
                anim.animator.cancel();
            }
            clear();
        }

        public void stop() {
            final int count = size();
            for (int i = 0; i < count; i++) {
                Tweener anim = get(i);
                anim.animator.end();
            }
            clear();
        }

        public void setSuspended(boolean suspend) {
            mSuspended = suspend;
        }
    };
这个是GlowPadView的内部类,继承ArrayList<Tweener>就能看出该类作用是统一管理一组动画。
    private AnimationBundle mWaveAnimations = new AnimationBundle();
    private AnimationBundle mTargetAnimations = new AnimationBundle();
    private AnimationBundle mGlowAnimations = new AnimationBundle();
有三个AnimationBundle成员,分别代表来电未按下动画,按下或放开瞬间的动画,手指头按下点PointCloud的动画。

mWaveAnimations 

  private void startWaveAnimation() {
        mWaveAnimations.cancel();
        mPointCloud.waveManager.setAlpha(1.0f);
        mPointCloud.waveManager.setRadius(mHandleDrawable.getWidth()/2.0f);
        mWaveAnimations.add(Tweener.to(mPointCloud.waveManager, WAVE_ANIMATION_DURATION,
                "ease", Ease.Quad.easeOut,
                "delay", 0,
                "radius", 2.0f * mOuterRadius,
                "onUpdate", mUpdateListener,
                "onComplete",
                new AnimatorListenerAdapter() {
                    public void onAnimationEnd(Animator animator) {
                        mPointCloud.waveManager.setRadius(0.0f);
                        mPointCloud.waveManager.setAlpha(0.0f);
                    }
                }));
        mWaveAnimations.start();
    }
PointCloud是从一个小圆扩散到大圆的点状动画。要变化的对象是mPointCloud.waveManager,它是PointCloud的内部类,定义了半径和alpha变量,然后在绘制方法draw中使用这些内部类变量,这个类是专门为动画而设计的;radius是绘制范围半径,从mHandleDrawable.getWidth()/2.0f->2.0f * mOuterRadius;ease的值就是取之前介绍的Ease中的常量;动画结束后设置半径为0、alpha为0。
注意来电时候这个动画是不断循环运行的,那么循环就是GlowPadWrapper中的triggerPing实现
    private void triggerPing() {
        Log.d(this, "triggerPing(): " + mPingEnabled + " " + this);
        if (mPingEnabled && !mPingHandler.hasMessages(PING_MESSAGE_WHAT)) {
            ping();

            if (ENABLE_PING_AUTO_REPEAT) {
                mPingHandler.sendEmptyMessageDelayed(PING_MESSAGE_WHAT, PING_REPEAT_DELAY_MS);
            }
        }
    }
 private final Handler mPingHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case PING_MESSAGE_WHAT:
                    triggerPing();
                    break;
            }
        }
    };
triggerPing中调用ping发起动画并延时发送一个消息,该消息的处理中又会调用triggerPing,这样就实现了无限循环。
  public void ping() {
        if (mFeedbackCount > 0) {
            boolean doWaveAnimation = true;
            final AnimationBundle waveAnimations = mWaveAnimations;

            // Don't do a wave if there's already one in progress
            if (waveAnimations.size() > 0 && waveAnimations.get(0).animator.isRunning()) {
                long t = waveAnimations.get(0).animator.getCurrentPlayTime();
                if (t < WAVE_ANIMATION_DURATION/2) {
                    doWaveAnimation = false;
                }
            }

            if (doWaveAnimation) {
                startWaveAnimation(); //开启动画
            }
        }
    }

mTargetAnimations 

这个包括两部分,一个是显示,另外一个是隐藏。
 private void hideTargets(boolean animate, boolean expanded) {
        mTargetAnimations.cancel();
        // Note: these animations should complete at the same time so that we can swap out
        // the target assets asynchronously from the setTargetResources() call.
        mAnimatingTargets = animate;
        final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
        final int delay = animate ? HIDE_ANIMATION_DELAY : 0;

        final float targetScale = expanded ?
                TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED;
        final int length = mTargetDrawables.size();
        final TimeInterpolator interpolator = Ease.Cubic.easeOut;
        for (int i = 0; i < length; i++) {   //循环中初始每个target的动画
            TargetDrawable target = mTargetDrawables.get(i);   
            target.setState(TargetDrawable.STATE_INACTIVE);
            mTargetAnimations.add(Tweener.to(target, duration,
                    "ease", interpolator,
                    "alpha", 0.0f, //value只有1个的话,表示动画是从当前的值变化到指定值,alpha从1->0
                    "scaleX", targetScale, //scale从1->0.8
                    "scaleY", targetScale,
                    "delay", delay,
                    "onUpdate", mUpdateListener));
        }

        float ringScaleTarget = expanded ?
                RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED;
        ringScaleTarget *= mRingScaleFactor;
        mTargetAnimations.add(Tweener.to(mOuterRing, duration,   //外部大圆环动画
                "ease", interpolator,
                "alpha", 0.0f,
                "scaleX", ringScaleTarget,
                "scaleY", ringScaleTarget,
                "delay", delay,
                "onUpdate", mUpdateListener,
                "onComplete", mTargetUpdateListener));

        mTargetAnimations.start();
    }

    private void showTargets(boolean animate) {
         ...
    }
两个动画相当于是相反的关系,介绍一个就够了。target就是按下时显示的接听、拒接及短信拒接按钮,动画同时包括了alpha和大小的变化,隐藏时是大小由1->0.8、alpha由1->0,就是视觉上缩小并消失的过程,显示的效果正好相反。除此之外还有个外层大圆环的动画,动画效果一样,这个大圆环显示了控件触摸的边界。

mGlowAnimations 

这个动画在按下后选中某个target时显示,也包括显示和隐藏两个动画

    private void showGlow(int duration, int delay, float finalAlpha,
            AnimatorListener finishListener) {
        mGlowAnimations.cancel();
        mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
                "ease", Ease.Cubic.easeIn,
                "delay", delay,
                "alpha", finalAlpha,
                "onUpdate", mUpdateListener,
                "onComplete", finishListener));
        mGlowAnimations.start();
    }

    private void hideGlow(int duration, int delay, float finalAlpha,
            AnimatorListener finishListener) {
        ...
    }
这里的mPointCloud.glowManager是PointCloud中另外一个内部类,除了alpha,半径外还包含了坐标x和y。两个方法调用的时候finalAlpha所赋的值都是0.0f,这个很奇怪,这样两个方法其实都是消失动画呀。还是有不同的,首先是动画变化速率不一样,其次是hideGlow还包含坐标的变化(动画结束后坐标恢复为0.0,这样方便运行mWaveAnimations )。showGlow其实的含义是showTarget,是选中某个target后突出显示target,然后表示手指所按位置的点状图消失,其实就是个PointCloud消失的动画。

背景动画

    private Tweener mBackgroundAnimator;
    private void startBackgroundAnimation(int duration, float alpha) {
        final Drawable background = getBackground();
        if (mAlwaysTrackFinger && background != null) {
            if (mBackgroundAnimator != null) {
                mBackgroundAnimator.animator.cancel();
            }
            mBackgroundAnimator = Tweener.to(background, duration,
                    "ease", Ease.Cubic.easeIn,
                    "alpha", (int)(255.0f * alpha),
                    "delay", SHOW_ANIMATION_DELAY);
            mBackgroundAnimator.animator.start();
        }
    }
背景alpha的动画,代码中未按下时背景的alpha值是0,按下后alpha值是1,因为需要背景来衬托接听、挂断等按键的显示。

TargetDrawable

GlowPadView有多个TargetDrawable成员变量
    private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>(); //按下后接听、挂断等图标
    private TargetDrawable mHandleDrawable; //未接听时正中间显示的图标
    private TargetDrawable mOuterRing;  //显示触摸边界的大圆环

初始化和位置设置

mTargetDrawables 包含了按下后所有的可选项。在构造方法中依据xml值初始化

        if (a.getValue(R.styleable.GlowPadView_targetDrawables, outValue)) {
            internalSetTargetResources(outValue.resourceId);
        }
当然还可以重新设置:

 public void setTargetResources(int resourceId) {
        if (mAnimatingTargets) {
            // postpone this change until we return to the initial state
            mNewTargetResources = resourceId;
        } else {
            internalSetTargetResources(resourceId);
        }
    }
重新设置的情况有几种,例如视频通话和语音通话的切换、对方开启主叫隐藏后无法短信拒接等。

 private void internalSetTargetResources(int resourceId) {
        final ArrayList<TargetDrawable> targets = loadDrawableArray(resourceId); //依据资源id创建对象
        mTargetDrawables = targets;
        mTargetResourceId = resourceId;

        int maxWidth = mHandleDrawable.getWidth();
        int maxHeight = mHandleDrawable.getHeight();
        final int count = targets.size();
        for (int i = 0; i < count; i++) {
            TargetDrawable target = targets.get(i);
            maxWidth = Math.max(maxWidth, target.getWidth());
            maxHeight = Math.max(maxHeight, target.getHeight());
        }
        if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) {
            mMaxTargetWidth = maxWidth;
            mMaxTargetHeight = maxHeight;
            requestLayout(); // required to resize layout and call updateTargetPositions()
        } else {
            updateTargetPositions(mWaveCenterX, mWaveCenterY);   //设置位置
            updatePointCloudPosition(mWaveCenterX, mWaveCenterY); //设置PointCloud位置
        }
    }
下面看位置设置

 private void updateTargetPositions(float centerX, float centerY) {
        // Reposition the target drawables if the view changed.
        ArrayList<TargetDrawable> targets = mTargetDrawables;
        final int size = targets.size();
        final float alpha = (float) (-2.0f * Math.PI / size);
        for (int i = 0; i < size; i++) {
            final TargetDrawable targetIcon = targets.get(i);
            final float angle = alpha * i;
            targetIcon.setPositionX(centerX);
            targetIcon.setPositionY(centerY);
            targetIcon.setX(getRingWidth() / 2 * (float) Math.cos(angle));
            targetIcon.setY(getRingHeight() / 2 * (float) Math.sin(angle));
        }
    }
size都是4,所以angel是90度,所以target都是间隔直角。那么两或者三个选项怎么也是90度?见xml中定义:

  <array name="incoming_call_widget_audio_without_sms_targets">
        <item>@drawable/ic_lockscreen_answer</item>
        <item>@null</item>
        <item>@drawable/ic_lockscreen_decline</item>
        <item>@null</item>"
    </array>

资源数组用空项占位置,所以所有情况下间隔角度都是90度。

状态切换

mTargetDrawables 选中和未选中显示的图片是不一样的,这个实现比较简单
    public static final int[] STATE_ACTIVE =
            { android.R.attr.state_enabled, android.R.attr.state_active };
    public static final int[] STATE_INACTIVE =
            { android.R.attr.state_enabled, -android.R.attr.state_active };
    public static final int[] STATE_FOCUSED =
            { android.R.attr.state_enabled, -android.R.attr.state_active,
                android.R.attr.state_focused };
TargetDrawble类中定义了三个状态。
    public void setState(int [] state) {
        if (mDrawable instanceof StateListDrawable) {
            StateListDrawable d = (StateListDrawable) mDrawable;
            d.setState(state);
        }
    }
实用setState即可切换图片,当然mDrawable要是StateListDrawable图片,例如接听按键图片
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_enabled="true" android:state_active="false" android:state_focused="false"
        android:drawable="@drawable/ic_lockscreen_answer_normal_layer"/>
    <item
        android:state_enabled="true" android:state_active="true"  android:state_focused="false"
        android:drawable="@drawable/ic_lockscreen_answer_activated_layer" />
   <item
        android:state_enabled="true" android:state_active="false"  android:state_focused="true"
        android:drawable="@drawable/ic_lockscreen_answer_activated_layer" />
</selector>

选中确认

private void handleMove(MotionEvent event) {
              ...
                final float snapRadius = mRingScaleFactor * mOuterRadius - mSnapMargin;
                final float snapDistance2 = snapRadius * snapRadius;
                // Find first target in range
                for (int i = 0; i < ntargets; i++) {
                    TargetDrawable target = targets.get(i);

                    double targetMinRad = (i - 0.5) * 2 * Math.PI / ntargets;
                    double targetMaxRad = (i + 0.5) * 2 * Math.PI / ntargets;
                    if (target.isEnabled()) {
                        boolean angleMatches =
                            (angleRad > targetMinRad && angleRad <= targetMaxRad) ||
                            (angleRad + 2 * Math.PI > targetMinRad &&
                             angleRad + 2 * Math.PI <= targetMaxRad);
                        if (angleMatches && (dist2(tx, ty) > snapDistance2)) {
                            activeTarget = i;
                        }
                    }
                }        
              ...
}
handleMove中处理mTargetDrawables 选中与否。ntargets介绍过永远是4,所以范围在正负45度之间且离原点的距离大于snapDistance2即是选中状态。
        mSnapMargin = a.getDimension(R.styleable.GlowPadView_snapMargin, mSnapMargin); //构造方法中初始化
这个距离是可以在xml中配置的。

控件绘制

@Override
    protected void onDraw(Canvas canvas) {
        mPointCloud.draw(canvas);
        mOuterRing.draw(canvas);
        final int ntargets = mTargetDrawables.size();
        for (int i = 0; i < ntargets; i++) {
            TargetDrawable target = mTargetDrawables.get(i);
            if (target != null) {
                target.draw(canvas);
            }
        }
        mHandleDrawable.draw(canvas);
    }
绘制统一在onDraw中进行,绘制的内容有mPointCloud、mOuterRing,mTargetDrawables和mHandleDrawable。可见所有东西都是绘制在屏幕上的,只是在不同的状态下alpha值不一样。
   private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() {
        public void onAnimationUpdate(ValueAnimator animation) {
            invalidate();
        }
    };
因为这四个东西是通过OnDraw绘制的,所以相关动画中的updateListener都是mUpdateListener,动作只有invalidate()一个,触发控件的重新绘制。

控件状态切换

    // Wave state machine
    private static final int STATE_IDLE = 0;  //空闲态
    private static final int STATE_START = 1; //开始按下,不过一般会马上切换到STATE_FIRST_TOUCH
    private static final int STATE_FIRST_TOUCH = 2; //第一次按下后还没有移动
    private static final int STATE_TRACKING = 3; //按下移动后但是没有target被选中
    private static final int STATE_SNAP = 4; //按下移动后有target被选中
    private static final int STATE_FINISH = 5; //手指抬起,在隐藏动画结束后会切换到STATE_IDLE
有五个状态,设置状态的方法是switchToState

 private void switchToState(int state, float x, float y) {
        switch (state) {
            case STATE_IDLE:  //隐藏mTargetDrawables,mPointCloud,mHandleDrawable
                deactivateTargets();
                /// M: need to hide target when change to idle state. @{
                hideTargets(false, false);
                /// @}
                hideGlow(0, 0, 0.0f, null);
                startBackgroundAnimation(0, 0.0f);
                mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
                mHandleDrawable.setAlpha(1.0f);
                break;

            case STATE_START:
                startBackgroundAnimation(0, 0.0f);
                break;

            case STATE_FIRST_TOUCH:  //隐藏mHandleDrawable,显示mTargetDrawables,设置背景alpha为1
                mHandleDrawable.setAlpha(0.0f);
                deactivateTargets();
                showTargets(true);
                startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f);
                setGrabbedState(OnTriggerListener.CENTER_HANDLE);
                ...
                break;

            case STATE_TRACKING:
                mHandleDrawable.setAlpha(0.0f);
                break;

            case STATE_SNAP:
                // TODO: Add transition states (see list_selector_background_transition.xml)
                mHandleDrawable.setAlpha(0.0f);
                showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 0.0f, null);
                break;

            case STATE_FINISH: //做隐藏动画,动画结束后切换到idle状态。
                doFinish();
                break;
        }
    }
引起状态变化的是手指的动作,见

  public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getActionMasked();
        boolean handled = false;
        switch (action) {
            case MotionEvent.ACTION_POINTER_DOWN:
            case MotionEvent.ACTION_DOWN:
                if (DEBUG) Log.v(TAG, "*** DOWN ***");
                handleDown(event);
                handleMove(event);
                handled = true;
                break;

            case MotionEvent.ACTION_MOVE:
                if (DEBUG) Log.v(TAG, "*** MOVE ***");
                handleMove(event);
                handled = true;
                break;

            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_UP:
                if (DEBUG) Log.v(TAG, "*** UP ***");
                handleMove(event);
                handleUp(event);
                handled = true;
                break;

            case MotionEvent.ACTION_CANCEL:
                if (DEBUG) Log.v(TAG, "*** CANCEL ***");
                handleMove(event);
                handleCancel(event);
                handled = true;
                break;
        }
        invalidate();
        return handled ? true : super.onTouchEvent(event);
    }
通过handleDown,handleMove,handleUp和handleCancel四个方法引发状态的切换。

控件事件触发

GlowPadView中定义了接口OnTriggerListener:
   public interface OnTriggerListener {
        int NO_HANDLE = 0;
        int CENTER_HANDLE = 1;
        public void onGrabbed(View v, int handle);
        public void onReleased(View v, int handle);
        public void onTrigger(View v, int target);
        public void onGrabbedStateChange(View v, int handle);
        public void onFinishFinalAnimation();
    }
GlowPadWrapper实现了该接口并且赋值给GlowPadView中的mOnTriggerListener,其中onFinishFinalAnimation和onGrabbedStateChange函数体为空,onGrabbed和onReleased开始或者结束mWaveAnimations的动画,最重要的是onTrigger
  public void onTrigger(View v, int target) {
        Log.d(this, "onTrigger() view=" + v + " target=" + target);
        final int resId = getResourceIdForTarget(target);
        switch (resId) {
            case R.drawable.ic_lockscreen_answer:  //接听来电
                mAnswerListener.onAnswer(VideoProfile.STATE_AUDIO_ONLY, getContext());
                mTargetTriggered = true;
                break;
            case R.drawable.ic_lockscreen_decline: //挂断来电
                mAnswerListener.onDecline(getContext());
                mTargetTriggered = true;
                break;
            case R.drawable.ic_lockscreen_text: //短信拒接
                mAnswerListener.onText();
                mTargetTriggered = true;
                break;
            ...
            default:
                // Code should never reach here.
                Log.e(this, "Trigger detected on unhandled resource. Skipping.");
        }
    }
其中mAnswerListener是个接口,定义在GlowPadWrapper中
   public interface AnswerListener {
        void onAnswer(int videoState, Context context);
        void onDecline(Context context);
        void onDeclineUpgradeRequest(Context context);
        void onText();
    }
实现是AnswerFragment,继而调用AnswerPresenter,然后继续往下调用TelecomAdapter,TelecomAdapter再往下已经超出InCallUI的代码范围了。

其它

还有一个成员
    private GlowpadExploreByTouchHelper mExploreByTouchHelper;
是无障碍辅助服务相关,这个实现有需要的自己分析,反正我是没见过有任何一个产品经理讨论过Android的AccessibilityService,我猜知道这个的人都不多。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值