ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

仿QQ对话列表滑动删除与置顶的原理及实现

2021-07-05 12:54:03  阅读:247  来源: 互联网

标签:QQ int newScrollX item 滑动 ListView LinearLayout 置顶


接下来,我们将完成QQ聊天界面的ListView滑动效果,大家可能都用过ListView,知道ListView是上下滑动的,并不会产生左右滑动的效果,如果想让ListView变成左右滑动的效果,必须对安卓源代码有所了解。

我的思路就是:

 

所有的屏幕操作事件由ListView作做拦截,同时把事件传递给SlideView做滑动,这种实现的确可以达到效果,而且代码很简单,根本不需要处理什么复杂的滑动冲突。

 

QQ效果图与自己实现的效果图对比:

 

                

 

 

1.思路流程

 

首先我们需要实现自己的ListView来处理截获屏幕的事件,但不是由ListView处理,而是转发给自定义item控件处理,也就是实现的SlideView控件。根据处理手势设置item的状态,也就是说当已经滑动了,这个时候如果不获取item的状态,下次在滑动这个item的时候是不知道这个控件已经滑动了,不然就会有二次滑动,所以必须保存滑动状态。

 

下面用箭头详细说明今天代码流程:

 

当手指滑动某个item的时候——>ListView截获滑动事件——>在自定义ListView中实现onTouchEvent()方法,判断当前是哪个item——>然后将事件派发给item自己的SlideView去处理滑动——>处理完成后设置当前item的状态(防止二次滑动,也防止其他item滑动后,该item没有恢复到原来的模样)

 

2.定义Item容器

 

代码如下:

 

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

    <LinearLayout
        android:id="@+id/view_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

    </LinearLayout>


    <LinearLayout
        android:id="@+id/holder"
        android:layout_width="120dp"
        android:layout_height="match_parent"
        android:background="#C5C1AA"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/top"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:gravity="center"
            android:background="#FFD700"
            android:text="@string/slide_holder_top"
            android:textSize="20sp"
            android:layout_weight="1"/>

        <TextView
            android:id="@+id/delete"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:gravity="center"
            android:text="@string/slide_holder_delete"
            android:textSize="20sp"
            android:layout_weight="1"/>

    </LinearLayout>
</merge>

 

这里需要说明的就是,merge节点在加载的时候会忽略掉视图的层级,也就是说加载是时候会自动忽略掉merge节点,直接加载内部的内容,这里就是两个LinearLayout了。

 

这里为什么要用这个,因为等会的SlideView会继承LinearLayout,如果用LinearLayout包裹这两个LinearLayout,就会多出一个本身没用LinearLayout,除了增加视力层级,没有任何效果。

 

第一个LinearLayout宽高都设置为match_parent,目的就是将后一个LinearLayout挤出屏幕之外。好达到有滑动出控件的效果。

 

3.实现SlideView

 

我们SlideView会继承自LinearLayout,当然你也可以继承其他的布局,只是LinearLayout考虑的相对简单点。

 

㈠定义item状态的接口

 

public interface OnSlideViewOnListener {
    //滑动的三个状态
    public static final int SLIDE_STATUS_OFF = 0;
    public static final int SLIDE_STATUS_START_SCROLL = 1;
    public static final int SLIDE_STATUS_ON = 2;

    /**
     * 更新滑动的状态
     *
     * @param view
     * @param status
     */
    public void Slide(View view, int status);
}

 

这个就不用多作解释了,一目了然。

 

㈡初始化SlideView

 

public void initView() {
    //获取上下文
    this.mContext = getContext();
    //初始化滑动对象
    this.mScroller = new Scroller(this.mContext);
    //设置该LinearLayout为横向坐标
    setOrientation(LinearLayout.HORIZONTAL);
    //加载布局
    View.inflate(this.mContext,R.layout.slide_holder,this);
    //获取容器
    this.mLinearLayout = (LinearLayout) findViewById(R.id.view_content);
    //将隐藏容器的宽度根据屏幕调节
    this.mHolderWidth = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.mHolderWidth, getResources().getDisplayMetrics()));
}

 

㈢处理ListView发送过来的事件

 

public void onRequiredToEvent(MotionEvent event) {
    //获取当前X,Y坐标
    int x = (int) event.getX();
    int y = (int) event.getY();
    Log.d(TAG, "x=" + x + " ,y=" + y);
    //getScrollX()获取的值则发生了变化:指调用的控件的水平移动的距离,当未移动的时候,获取的值为0. 当向右移动20,则获取值为 -20,再向右移动10,则获取-30
    int scrollX = getScrollX();
    //根据按键的方式处理相应的事件
    switch (event.getAction()) {
        //按下时候处理
        case MotionEvent.ACTION_DOWN: {
            if (!this.mScroller.isFinished()) {
                this.mScroller.abortAnimation();
            }
            if (this.mOnSlideViewOnListener != null) {
                this.mOnSlideViewOnListener.Slide(this, OnSlideViewOnListener.SLIDE_STATUS_START_SCROLL);
            }
            break;
        }
        //移动时候处理
        case MotionEvent.ACTION_MOVE: {
            //x,y各滑动了多少距离
            int deltaX = x - this.mLastX;
            int deltaY = y - this.mLastY;
            Log.d(TAG, "deltaX=" + deltaX + " ,deltaY=" + deltaY);
            if (Math.abs(deltaX) < Math.abs(deltaY) * 2) {
                //角度大于60度,不符合滑动的条件
                break;
            }
            int newScrollX = scrollX - deltaX;
            if (deltaX != 0) {
                if (newScrollX < 0) {
                    newScrollX = 0;
                } else if (newScrollX > this.mHolderWidth) {
                    newScrollX = this.mHolderWidth;
                }
                this.scrollTo(newScrollX, 0);
            }
            break;
        }
        //抬起时候处理
        case MotionEvent.ACTION_UP: {
            int newScrollX = 0;
            if (scrollX - this.mHolderWidth * 0.75 > 0) {
                newScrollX = this.mHolderWidth;
            }
            this.smoothScrollTo(newScrollX, 0);
            if (this.mOnSlideViewOnListener != null) {
                this.mOnSlideViewOnListener.Slide(this, newScrollX == 0 ? OnSlideViewOnListener.SLIDE_STATUS_OFF : OnSlideViewOnListener.SLIDE_STATUS_ON);
            }
            break;
        }
        default:
            break;
    }

    //将历史坐标更新
    this.mLastX = x;
    this.mLastY = y;
}

 

在开始的时候定义了一个Scroller对象,该对象是弹性滑动对象,滑动效果由他实现。

 

Math.abs(deltaX) < Math.abs(deltaY) * 2的解释:

 

deltaX为滑动的坐标变量,X向左坐标越小,X向右坐标越大,Y向下坐标越大,Y向上坐标越小,所以有可能相对于上一个坐标变量相减会是负值,所以用Math.abs()取他们的绝对值,因为我们只要保证横向滑动的时候,角度在60度以内就算是横向滑动,大于60度,那就算纵向滑动所以不处理滑动。而tan(63)约等于2,故对边除邻边要<2,所以把变更Y移过去就得到这个值。

 

当然图像更容易理解,如下所示:

 

 

大的直线箭头为X,Y轴,120度的箭头为滑动方向,我们在该图中为左,在矩形ListView的Item这个120度角度内都判断为向左滑动,如果手指超过这个角度,就不是横向滑动了。

 

getScrollX()指调用的控件的水平移动的距离,当未移动的时候,获取的值为0. 当向右移动20,则获取值为 -20,再向右移动10,则获取-30

 

this.mScroller.isFinished():

 

返回scroller是否已完成滚动。

返回值:停止滚动返回true,否则返回false

 

this.mScroller.abortAnimation():停止动画。

 

 

标签:QQ,int,newScrollX,item,滑动,ListView,LinearLayout,置顶
来源: https://blog.51cto.com/liyuanjinglyj/2978805

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有