Android新体会(二)仿桌面实现图标拖动

这篇博客主要讲解如何代码实现拖动图标功能。阅读本篇博客之前请先阅读上一篇博客,了解一下事件分发机制的一些要点。

Android新体会(一)通过仿桌面实现图标拖动了解事件分发机制

然后是demo地址https://github.com/huangwanjie/TableImitate/tree/develop

效果图

实现拖动的actvtiy包含一个ViewPager和一个CellDragLayout。ViewPager包含两个fragment,fragment中的布局有一个RecyclerView。CellDragLayout是实现图标移动的类。首先看一下布局activity布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.activity.MainActivity">
    <FrameLayout
      android:id="@+id/fl_under"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
        <android.support.v4.view.ViewPager
          android:id="@+id/vp_cells"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:layout_marginTop="100dp"/>
    </FrameLayout>

    <com.hwj.test.tabledrag.celldrag.CellDragLayout
        android:id="@+id/cdl_drag"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

然后看fragment布局

<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rv_cells"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</android.support.v7.widget.RecyclerView>

最后RecyclerView的item的布局:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp">

    <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/iv_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:scaleType="fitXY"/>
</FrameLayout>

说一下实现的大概思路:

1.长按RecyclerView中的item,然后在CellDragLayout上新建一个ImageView,把item的bitmap给到ImageView,同时将item设为View.INVISIBLE.

2.让ImageView随着CellDragLayout接收到的Move事件而移动。

3.松开手后remove掉ImageView,同时item设为View.VISIBLE。

这里的重点主要是第一步和第二步的衔接。上一篇博客中提到,在ViewGroup事件分发中,Down事件是整个事件的开始,消费了Down时间的view,ViewGroup在不拦截的情况下会把后续的事件都交给该view去处理,不再访问分发到其它子view,也就是在事件分发的流程中消费事件都是同一个View。所以在正常事件分发流程中,item获取了并消费事件之后,RelativeLayout不可能在本次触摸事件结束之后将事件在分发给CellDraglayout。因此实际上我们需要解决的问题是,CellDragLayout和item都必须能接收和消费到相同的事件。由于CellDragLayout布局中的最上层,所以它能很容易的获取到事件,并进行消费,但是消费过后RelativeLayout不会把事件间接地分发给item了。所以按下的时候,先判断该位置是否在ViewPager所在的区域中,如果是,CellDragLayout在onTouchEvent(MotionEvent event)中消费事件,同时需要主动地将事件分发给activity_main.xml中的FrameLayout,调用FrameLayout的dispatchTouchEvent(MotionEvent event)方法,把CellDragLayout接收到的event传进去,item自然就可以获取到事件了。

下面把CellDragLayout中重写的onTouchEvent()方法贴出来

public boolean onTouchEvent(MotionEvent event) {

    Log.i("onTouchEvent", event.toString());

    if (actionDownRect == null){ //1
      return false;
    }

    currentTouchX = event.getX();
    currentTouchY = event.getY();

    if (event.getAction() == MotionEvent.ACTION_DOWN){ //2
      if (!actionDownRect.contains((int) event.getRawX(), (int) event.getRawY())) {
        return false;
      }
    }

    if (moveView == null){ //3
      deleteCellHelper.transferTouchEvent(event);
      return true;
    }

    switch (event.getAction()){
      case MotionEvent.ACTION_MOVE: //4
        moveView.setTranslationX(currentTouchX - (moveView.getX() + moveView.getWidth() / 2) + moveView.getTranslationX());
        moveView.setTranslationY(currentTouchY - (moveView.getY() + moveView.getHeight() / 2) + moveView.getTranslationY());
        onMoveState((int)(event.getX()), (int)(event.getY()));
        break;
      case MotionEvent.ACTION_UP:
        onUpState((int)(event.getX()), (int)(event.getY()));
        break;
      default:
        break;
    }
    return true;
  }

注释1中的actionDownRect是ViewPager相对于CellDragLayout的区域,要是没有设置该区域就不消费事件。注释2中判断触点是否在actionDownRect之中,不在就不消费事件。注释3中moveView是在CellDragLayout上移动的控件,在item触发长按是创建,为空表示还没有触发,侧通过一个辅助类DeleteCellHelper调用transferTouchEvent(MotionEvent event),把事件传递出去。注释4是在长按时候moveView创建之后执行,使moveView根据触点移动。下面看看DeleteCellHelper类部分代码:

public class DeleteCellHelper<T> {

  private CellDragLayout dragLayout;
  private View underLayout;
  private T content;
  View view;

  public DeleteCellHelper(CellDragLayout dragLayout,View underLayout){

    this.dragLayout = dragLayout;
    this.underLayout = underLayout;
    dragLayout.setDeleteCellHelper(this);
  }

  public void setDownActionRect(Rect rect){
    dragLayout.setActionDownRect(rect);
  }

  public void onAction(View view, T content){
    this.view = view;
    this.content = content;
    viewCopy(view);
    view.setVisibility(View.INVISIBLE);
  }

  private void viewCopy(View view){
    Bitmap bitmap = ViewUtil.getViewBitmap(view);
    ImageView ivCopy = new ImageView(view.getContext());
    ivCopy.setClickable(false);
    Rect rect = new Rect();
    view.getGlobalVisibleRect(rect);
    LayoutParams lp = new LayoutParams(view.getWidth(), view.getHeight());
    lp.leftMargin = rect.left;
    lp.topMargin = rect.top - (int) dragLayout.getX();
    ivCopy.setLayoutParams(lp);
    ivCopy.setImageBitmap(bitmap);
    dragLayout.addMoveView(ivCopy, bitmap);
  }

  public void cancle(){
    view.setVisibility(View.VISIBLE);
    if (deleteListenter != null){
      deleteListenter.onCancle();
    }
  }

  public void deleted(){
    if (deleteListenter != null){
      deleteListenter.onDeteled(content);
    }
  }

  private DeleteListenter deleteListenter;

  public void setDeleteListenter(DeleteListenter deleteListenter){
    this.deleteListenter = deleteListenter;
  }

  public void transferTouchEvent(MotionEvent event){
    underLayout.dispatchTouchEvent(event);
  }

  public interface DeleteListenter<T>{
    void onCancle();
    void onDeteled(T content);
  }
}

underLayout是demo中的activity_main.xml的FrameLayout,创建DeleteCellHelper时把该对象传进去。item在长按之后调用onAtion(View v, T content)把item中的view和相关内容content传进来(content现在还没有用,下一篇将介绍它的用处),进在draglayout中新建用于移动的ImageView。DeleteCellHelper类主要的作用是让CellDragLayout和其它的控件建立联系,充当桥梁的作用。

还需要注意的一点是demo中的underLayout和draglayout在布局中的大小和位置是一样的,因为draglayout分发给underLayout的event是相对于underLayout的,里面包含的位置信息event.getX()和getY()也是相对于dragLayout的,要是underLayout和draglayout在布局中位置不一致需要则经过转换,不然触点在underLayout会表现的不准确。

如果需要转换要怎么写呢?我们可以参考一下ViewGroup中的dispatchTransformedTouchEvent方法,里面有这样一段转换得代码:

           if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }

计算完偏移量之后通过event.offsetLocation进行校正,然后在传给子view,最后再更正回来。

好了本篇博客到此结束,下篇分享删除的动画特效。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值