Android进阶程序员

深入剖析锤子onestep代码实现 - 下篇 - 拖拽分享

2017-03-19  本文已影响252人  YY17

onestep拖拽分享功能代码分析

文章结构

拖拽原理

拖拽事件派发与处理 - ACTION_DRAG_STARTED, ACTION_DRAG_ENTERED, ACTION_DROP, ACTION_DRAG_EXITED

备注:
SideView实际上包含了多个SidebarListView的子视图实例(见上篇),这里仅以sharelist子视图为例来分析和画图,layout为

    <com.smartisanos.sidebar.view.SidebarListView
            android:id="@+id/sharelist"

与其配套的相关类为ResolveInfoListAdapter, ResolveInfoGroup, ResolveInfoManager

DragShare_START.jpg DragShare_ENTER_DROP.jpg

ViewRootImpl会按View层级,往下调用视图的dispatchDragEvent方法派发拖拽事。
即window->viewroot->decor->...->sideview->...
SideView重写了dispatchDragEvent()方法,并对ACTION_DRAG_STARTED进行了处理:

class SideView{
      @Override
      public boolean dispatchDragEvent(DragEvent event) {
        case DragEvent.ACTION_DRAG_STARTED:
            FloatText.getInstance(mContext).start();//图标悬浮文字
            onDragStart(event);//拖拽前的预备动作,下述
            //最终回调到ViewGroup.dispatchDragEvent(),让其执行默认派发逻辑,
            //最终会遍历所有的子视图,调用其Drag事件监听器
            return super.dispatchDragEvent(event);
        ... ...
}

//拖拽前的预备动作, 切换普通模式显示列表和分享组件列表
//普通模式,onestep刚开始显示时的状态,显示APP图标、联系人,此时你可以切换应用,点击图标打开应用、联系人
//拖拽分享模式,比如拖拽一堆图片要分享给联系人或目标应用
private void onDragStart(final DragEvent event) {
    //停止正在进行的切换动画
    if (mSwitchContentAnim != null) {
        mSwitchContentAnim.cancel();
    }
    int deltaWidth = mContext.getResources().getDimensionPixelSize(R.dimen.sidebar_list_anim_padding);
    boolean leftMode = (SidebarController.getInstance(mContext).getSidebarMode() == SidebarMode.MODE_LEFT);
    int width = getWidth() + deltaWidth;
    int outTo = leftMode ? -width : width;
    //新建切换动画
    mSwitchContentAnim = new AnimTimeLine();
    int time = 300;
    final List<View> disappearViews = new ArrayList<View>();
    //需要消失的图标,即普通模式下显示时的三个SidebarListView列表里的APP图标、联系人图标
    disappearViews.addAll(mOngoingList.getViewList());
    disappearViews.addAll(mContactList.getViewList());
    disappearViews.addAll(mAppList.getViewList());
    if (disappearViews.size() > 0) {
        Vector3f scaleFrom = new Vector3f(1, 1);
        Vector3f scaleTo   = new Vector3f(0.2f, 0.2f);
        Vector3f alphaFrom = new Vector3f(0, 0, 1);
        Vector3f alphaTo   = new Vector3f(0, 0, 0);
        int count = disappearViews.size();
        for (int i = 0; i < count; i++) {
            View view = disappearViews.get(i);
            Anim scale = new Anim(view, Anim.SCALE, time, Anim.CUBIC_OUT, scaleFrom, scaleTo);
            Anim alpha = new Anim(view, Anim.TRANSPARENT, time, Anim.CUBIC_OUT, alphaFrom, alphaTo);
            //动画效果,透明度渐变为0,缩小
            mSwitchContentAnim.addAnim(scale);
            mSwitchContentAnim.addAnim(alpha);
        }
}
//更新支持分享的图标列表,见下文
    if (event != null) {
        mOngoingListFake.onDragStart(event);
        mContactListFake.onDragStart(event);
        mShareList.onDragStart(event);
    }
    mScrollViewDragged.setTranslationX(outTo);
    Anim inAnim = new Anim(mScrollViewDragged, Anim.MOVE, time, Anim.CUBIC_OUT, new Vector3f(outTo, 0), new Vector3f());
    inAnim.setDelay(time / 4);
    mSwitchContentAnim.addAnim(inAnim);
    mSwitchContentAnim.setAnimListener(new AnimListener() {
        @Override
        public void onStart() {
            mScrollViewDragged.setVisibility(VISIBLE);
        }

        //切换动画结束
        @Override
        public void onComplete(int type) {
            if (mSwitchContentAnim != null) {
                int count = disappearViews.size();
                for (int i = 0; i < count; i++) {
                    View view = disappearViews.get(i);
                    view.setAlpha(1);
                    view.setScaleX(1);
                    view.setScaleY(1);
                }
                mScrollViewNormal.setVisibility(GONE);//普通模式的列表消失,不显示
                mScrollViewDragged.setVisibility(VISIBLE);//显示用于分享用的列表
                mScrollViewDragged.setTranslationX(0);
                mSwitchContentAnim = null;
            }
        }
    });
    mSwitchContentAnim.start();//切换动画开始
}
}

列表视图的处理:

class SidebarListView{
  public void onDragEnd() {
    if (mAdapter != null) {
        mAdapter.onDragStart();//转给适配器处理
    }
  }
}

ResolveInfoListAdapter的处理:

class ResolveInfoListAdapter{
@Override
public void onDragStart(DragEvent event) {
    ... ...
    updateAcceptableResolveInfos();
}
//更新支持分享的图标列表
private void updateAcceptableResolveInfos() {
    mAcceptableResolveInfos.clear();
    for (ResolveInfoGroup rig : mResolveInfos) {
        //acceptDragEvent原来是用来判断能否接收拖拽事件的,命名canAcceptDragEvent或许更合适
        if (mDragEvent == null || rig.acceptDragEvent(mContext, mDragEvent)) {
            mAcceptableResolveInfos.add(rig);
        }
    }
    notifyDataSetChanged();//更新视图
}
}

分享图标条目视图控件类的处理:

class ResolveInfoGroup{
  public boolean acceptDragEvent(Context context, DragEvent event) {
    //获取拖拽事件的MIME类型,这是MIMETYPE_TEXT_PLAIN,纯文本
    if (ClipDescription.MIMETYPE_TEXT_PLAIN.equals(mimeType)) {//纯文本类型
        //构建查询用的intent
        Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
        sharingIntent.setType("text/plain");
        sharingIntent.setPackage(getPackageName());
        List<ResolveInfo> infos = context.getPackageManager().queryIntentActivities(sharingIntent, 0);
        if (infos != null) {
            for (ComponentName name : mNames) {
                for (ResolveInfo ri2 : infos) {
                    //如果在pm中查询到符合intent过滤出来的组件,且与本图标条目的组件名称name相同,
                    //则认为该图标是可分享目标
                    if (name.equals(new ComponentName(ri2.activityInfo.packageName, ri2.activityInfo.name))) {
                        return true;
                    }
                }
            }
        }
    } else {//其它MIME类型
        Intent intent = new Intent();
        if (event.getClipDescription().getMimeTypeCount() > 1) {
            intent.setAction(Intent.ACTION_SEND_MULTIPLE);
        } else {
            intent.setAction(Intent.ACTION_SEND);
        }
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.setType(mimeType);
        intent.setPackage(getPackageName());
        List<ResolveInfo> infos = context.getPackageManager().queryIntentActivities(intent, 0);
        if (infos != null) {
            for (ComponentName name : mNames) {
                for (ResolveInfo ri2 : infos) {
                    //如果在pm中查询到符合intent过滤出来的组件,且与本图标条目的组件名称name相同,
                    //则认为该图标是可分享目标
                    if (name.equals(new ComponentName(ri2.activityInfo.packageName, ri2.activityInfo.name))) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}
}
}

一键拖拽分享,侧边栏出来的图标,就是这么搜出来的,过滤出有在Manifest.xml里声明ACTION_SEND和text/plain或其它MIME类型的组件。
所以你的应用想在拖拽时,出现在onestep的侧边栏里,就应该对能接收分享的Activity,加上:

<intent-filter>
  <action android:name="android.intent.action.SEND" />
  <category android:name="android.intent.category.DEFAULT" />
  <data android:mimeType="text/*" />
</intent-filter>

当然,除此之外,在onestep里的设置里,要选中该分享组件。

继续拖拽事件的派发

//前面SideView.dispatchDragEvent()最后会调用super.dispatchDragEvent(),
//即ViewGroup.dispatchDragEvent(),让其执行默认派发逻辑,最终会遍历所有的子视图,调用其Drag事件监听器
//每个分享组件图标都在ResolveInfoListAdapter.getView()时进行了Drag事件监听器的注册
//由于每个图标视图都对ACTION_DRAG_STARTED事件返回了true,因此,此后的Drag事件都会派发到此来处理。
class ResolveInfoListAdapter{
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    final ViewHolder holder;
    ... ...
    //为每一个分享组件图标注册拖拽事件监听器
    holder.view.setOnDragListener(new View.OnDragListener() {
        @Override
        public boolean onDrag(View v, DragEvent event) {
            final int action = event.getAction();
            switch (action) {//按SDK文档,注册OnDragListener,对下列五个动作进行处理
                case DragEvent.ACTION_DRAG_STARTED:
                    return true;
                    //有View发起了startDrag()调用,系统开始处理拖拽事件,并通知所有视图,ACTION_DRAG_STARTED,
                    //我们返回true,表示我们感兴趣,要处理。文档说了,ACTION_DRAG_STARTED会派发给所有注册了dragListener的视图,
                    //但只有此时返回true,后面的事件才会继续派发给你。

                case DragEvent.ACTION_DRAG_ENTERED: //拖拽图像进入到我们的分享组件图标视图区域内了,来个动画
                    FloatText.getInstance(mContext).show(holder.view, holder.resolveInfoGroup.getDisplayName());//显示悬浮文字
                    holder.view.animate().scaleX(SCALE_SIZE).scaleY(SCALE_SIZE)
                    .setInterpolator(new AccelerateDecelerateInterpolator())
                    .setStartDelay(0)
                    .setDuration(100).start();
                    return true;
                case DragEvent.ACTION_DRAG_EXITED://用户从图标区域拖出去了,来个动画表示表示,把现场还原
                    FloatText.getInstance(mContext).hide();//悬浮提示文字,消失
                    holder.view.animate().scaleX(1.0f).scaleY(1.0f).setDuration(100).start();
                    return true;
                case DragEvent.ACTION_DRAG_LOCATION://不需要处理,其实是给父容器DragScrollView处理,处理拖动的事情
//                        log.d("ACTION_DRAG_LOCATION");
                    return true;
                case DragEvent.ACTION_DROP://用户放进来了我们View的Bounding圈圈里
//                        log.d("ACTION_DRAG_DROP");
                    holder.view.animate().scaleX(1.0f).scaleY(1.0f).setDuration(100).start();
                    //把拖拽事件信息丢给图标条目类去处理,最终就是启动分享组件的页面
                    return holder.resolveInfoGroup.handleDragEvent(mContext, event);
                case DragEvent.ACTION_DRAG_ENDED:
                    return true;
            }
            return false;
        }
    });
    return holder.view;
}
}

拖拽结束,ACTION_DRAG_ENDED

DragShare_END.jpg
class SideView{
    @Override
public boolean dispatchDragEvent(DragEvent event) {
    int action = event.getAction();
    switch (action) {
    case DragEvent.ACTION_DRAG_ENDED:
        FloatText.getInstance(mContext).end();//悬浮文字消失
        boolean ret = super.dispatchDragEvent(event);//依然让ViewGroup去派发到每个分享组件图标的OnDragListener,见上ResolveInfoListAdapter中的OnDragListener
        onDragEnd(event);//善后处理,见下
        return ret;
    }
    ... ...
}

private void onDragEnd(DragEvent event) {
    if (mSwitchContentAnim != null) {
        mSwitchContentAnim.cancel();
    }
    int deltaWidth = mContext.getResources().getDimensionPixelSize(R.dimen.sidebar_list_anim_padding);
    boolean leftMode = (SidebarController.getInstance(mContext).getSidebarMode() == SidebarMode.MODE_LEFT);
    int width = getWidth() + deltaWidth;
    int outTo = leftMode ? -width : width;
    mSwitchContentAnim = new AnimTimeLine();
    int time = 300;
    //拖拽时消失的那三个列表,现在要用动画切换回来
    //对所有图标增加放大、透明度渐加进入的效果
    final List<View> disappearViews = new ArrayList<View>();
    disappearViews.addAll(mOngoingList.getViewList());
    disappearViews.addAll(mContactList.getViewList());
    disappearViews.addAll(mAppList.getViewList());
    int subViewCount = disappearViews.size();
    if (subViewCount > 0) {
        AnimTimeLine timeLine = new AnimTimeLine();
        Vector3f scaleFrom = new Vector3f(0.2f, 0.2f);
        Vector3f scaleTo   = new Vector3f(1, 1);
        Vector3f alphaFrom = new Vector3f(0, 0, 0);
        Vector3f alphaTo   = new Vector3f(0, 0, 1);
        for (int i = 0; i < subViewCount; i++) {
            View view = disappearViews.get(i);
            Anim scale = new Anim(view, Anim.SCALE, time, Anim.CUBIC_OUT, scaleFrom, scaleTo);
            Anim alpha = new Anim(view, Anim.TRANSPARENT, time, Anim.CUBIC_OUT, alphaFrom, alphaTo);
            timeLine.addAnim(scale);
            timeLine.addAnim(alpha);
        }
        timeLine.setDelay(time / 4);
        mSwitchContentAnim.addTimeLine(timeLine);
    }
    //对侧边栏的bar,mScrollViewDragged,也是承载所有拖拽时的列表的父容器
    //添加移动退出的效果
    Anim outAnim = new Anim(mScrollViewDragged, Anim.MOVE, time, Anim.CUBIC_OUT, new Vector3f(), new Vector3f(outTo, 0));
    mSwitchContentAnim.addAnim(outAnim);
    mSwitchContentAnim.setAnimListener(new AnimListener() {
        @Override
        public void onStart() {
            //动画开始, 就显示普通模式的bar,mScrollViewNormal
            //难怪,侧边栏切换时,看到的是有两条黑色底边的bar在,其中一个在退出,即mScrollViewDragged
            mScrollViewNormal.setVisibility(VISIBLE);
        }
        
        @Override
        public void onComplete(int type) {//动画结束
            if (mSwitchContentAnim != null) {
                int count = disappearViews.size();
                //动画结束,所有普通模式下的图标,全部显示回来
                for (int i = 0; i < count; i++) {
                    View view = disappearViews.get(i);
                    view.setAlpha(1);
                    view.setScaleX(1);
                    view.setScaleY(1);
                }
                //动画结束,拖拽模式下的列表控件,善后处理,见下
                mOngoingListFake.onDragEnd();
                mContactListFake.onDragEnd();
                mShareList.onDragEnd();
                //最终,显示普通模式的bar,拖拽模式的bar消失
                mScrollViewNormal.setVisibility(VISIBLE);
                mScrollViewDragged.setTranslationX(0);
                mScrollViewDragged.setVisibility(GONE);
                mScrollViewDragged.scrollTo(0, 0);
                mSwitchContentAnim = null;
            }
        }
    });
    //所有图标切换动画,开始
    mSwitchContentAnim.start();
}
}

列表视图的处理:

class SidebarListView{
  public void onDragEnd() {
    if (mAdapter != null) {
        mAdapter.onDragEnd();//转给适配器处理
    }
  }
}

适配器的处理:

class ResolveInfoListAdapter{
@Override
public void onDragEnd() {
    ... ...
    mDragEvent = null;
    updateAcceptableResolveInfos();
}
//更新支持分享的图标列表
private void updateAcceptableResolveInfos() {
    mAcceptableResolveInfos.clear();
    for (ResolveInfoGroup rig : mResolveInfos) {
        //mDragEvent在onDragEnd()被置null,这里还进来更新,有啥意义? 况且,分享列表即将被隐藏.
        if (mDragEvent == null || rig.acceptDragEvent(mContext, mDragEvent)) {
            mAcceptableResolveInfos.add(rig);
        }
    }
    notifyDataSetChanged();//更新视图
}
}

至此,拖拽从ACTION_DRAG_STARTED开始派发,到普通模式与拖拽模式两层列表的动画切换,所有图标
动画效果切换,到用户放开手,或拖拽没放置进图标区域而离开,或成功放进图标进行分享,
到最终动画结束这一切。整个流程就都在这了?错,还少一个,拖着图标在侧边栏中移动!
即ACTION_DRAG_LOCATION事件

拖拽移动 - ACTION_DRAG_LOCATION

上述流程中,没有哪个对象对ACTION_DRAG_LOCATION事件进行处理,事实上,它被派发到比SideView的父容器
DragScrollView当中去了,看它怎么处理:

DragShare_Location.jpg
class DragScrollView{
    @Override
    public boolean dispatchDragEvent(DragEvent event) {
      //丢给内部类ScrollController处理
      boolean intercept = mScrollController.onDragEvent(event);
      if (intercept) {
          return true;
      }
      return super.dispatchDragEvent(event);
    }

    private class ScrollController {
      public boolean onDragEvent(DragEvent event){
        int action = event.getAction();
        if(action ==  DragEvent.ACTION_DRAG_LOCATION){//专门处理ACTION_DRAG_LOCATION事件
            float y = event.getY();//获取拖拽视图的Y坐标
            //滚动侧边栏
            if(y < mTopArea && !isScrollUp()){
                setEvent(event);
                setRate((mTopArea - y)  * 1.0f / mTopArea);
                scroll(DIRECTION_DOWN);//向下滚动
                return true;
            }else if(y > mView.getHeight() - mBottomArea && !isScrollBottom()){
                setEvent(event);
                setRate(1 - (mView.getHeight() - y)  * 1.0f / mBottomArea);
                scroll(DIRECTION_UP);//向上滚动
                return true;
            }
        }
        //其它事件,不做处理。
        setEvent(null);
        setRate(0.0f);
        scroll(DIRECTION_NONE);//空处理
        return false;
    }

    private void scroll(int direction) {
        mScrollDirection = direction;
        mView.removeCallbacks(mScrollRunnable);
        if (mScrollDirection != DIRECTION_NONE) {
            mInitDel = mView.getHeight() / NUMBS;
            if (mInitDel <= 0) {
                mInitDel = 1;
            }
            //丢个消息给主线程,呆会处理
            mView.post(mScrollRunnable);
        }
    }

     private Runnable mScrollRunnable = new Runnable() {
        @Override
        public void run() {
            //滚动DragScrollView
            mView.scrollBy(0, (int) (mInitDel * mScrollDirection * mRate));
            //又来一发,不断滚动,
            //直到onDragEvent中条件不成立,会removeCallbacks(mScrollRunnable),停止滚动
            mView.postDelayed(mScrollRunnable, DELAY);
            if (mEvent != null) {
                DragScrollView.super.dispatchDragEvent(mEvent);
            }
        }
    };    
}
}
上一篇 下一篇

猜你喜欢

热点阅读