仿朋友圈、微博,下滑关闭大图---智能识别手势布局-SmartTouchLayout

本文介绍了一种名为SmartTouchLayout的库,用于在拥有多种元素(图片、按钮、文本、视频)的界面中实现类似微信朋友圈的大图滑动退出效果,同时支持双指缩放和单击/双击操作。关键代码展示了手势识别和事件分发的过程。

需求来源:

产品需要实现跟微信朋友圈看大图可下滑退出的效果,

但项目中不仅有大图,图中还有按钮,有文本,有视频等等,总之布局很复杂

反正这些他不管,就是要下滑退出,再缩回上一层界面小图片位置。

找了不少DEMO,基本都是只满足图片,视频实现这个功能, 那就自己来

 

SmartTouchLayout

支持多手势识别的布局

双指、双击缩放;单指滑动;下滑退出;单击退出;不影响子控件事件;与ViewPage不冲突;

无脑调用:

FrameLayout怎么用,它就怎么用。

 

直接上图

 

双指、双击缩放;下滑退出到指定位置;

 

不影响子控件事件;不指定位置时,下滑到底部消失;

 

与ViewPage不冲突;

 

源码传送门:

https://github.com/evening424/SmartTouchLayout

 

技术要点:

 通过计算点击的时间差,判断是单击还是双击

private float firstClickX, firstClickY, secondClickX, secondClickY;

    // 统计?ms内的点击次数

    private TouchEventCountThread mInTouchEventCount = new TouchEventCountThread();

    // 根据TouchEventCountThread统计到的点击次数, perform单击还是双击事件

    private TouchEventHandler mTouchEventHandler = new TouchEventHandler();

    private void checkClickDown(MotionEvent ev){

        if (0 == mInTouchEventCount.touchCount) { // 第一次按下时,开始统计

            //Log.i(TAG , "checkClickDown 第一次按下时,开始统计" );

            postDelayed(mInTouchEventCount, DOUBLE_CLICK_TIME_OFFSET);

        }

    }

    private void checkClickUp(float clickX, float clickY){

        //Log.i(TAG , "checkClickUp clickX:" + clickX + ",clickY:" + clickY);

        // 一次点击事件要有按下和抬起, 有抬起必有按下, 所以只需要在ACTION_UP中处理

        if (!mInTouchEventCount.isLongClick) {

            mInTouchEventCount.touchCount++;

            if(mInTouchEventCount.touchCount == 1){

                firstClickX = clickX;

                firstClickY = clickY;

                //Log.i(TAG , "checkClickUp 点击第一下");

            }else if(mInTouchEventCount.touchCount == 2){

                secondClickX = clickX;

                secondClickY = clickY;

                float xOff = Math.abs(firstClickX - secondClickX);

                float yOff = Math.abs(firstClickY - secondClickY);

                //两次点击距离相近

                if(xOff < 60 && yOff < 60 ){

                    //Double click 成立

                    //Log.i(TAG , "checkClickUp Double click 成立");

                }else{

                    //Double click 不成立,当单击处理

                    mInTouchEventCount.touchCount = 1;

                    //Log.i(TAG , "checkClickUp Double click 不成立,当单击处理");

                }

            }else{

                mInTouchEventCount.touchCount = 0;

                //Log.i(TAG , "checkClickUp 复原");

            }

        }else {

            // 长按复原

            mInTouchEventCount.isLongClick = false;

            //Log.i(TAG , "checkClickUp 长按复原");

        }

    }

    private class TouchEventCountThread implements Runnable {

        public int touchCount = 0;

        public boolean isLongClick = false;

        @Override

        public void run() {

            Message msg = new Message();

            if(0 == touchCount){ // long click

                isLongClick = true;

            } else {

                msg.arg1 = touchCount;

                mTouchEventHandler.sendMessage(msg);

                touchCount = 0;

            }

            //Log.i(TAG , "TouchEventCountThread 结束:" + touchCount);

        }

    }

    private class TouchEventHandler extends Handler {

        @Override

        public void handleMessage(Message msg) {

            //Log.i(TAG, "touch " + msg.arg1 + " time.");

            if(msg.arg1 == 1){

                onSingleClicked(oldX, oldY);

            }else{

                onDoubleClicked(oldX, oldY);

            }

        }

    }

如果单击,把事件向子VIEW传递

public boolean onInterceptTouchEvent(MotionEvent ev) {

    final int action = ev.getAction();

    if(action == MotionEvent.ACTION_MOVE && mTouchState == TOUCH_MYSELF){

        //Log.i(TAG, "拦截 为自己处理");

        return true;

    }

    switch (action) {

        case MotionEvent.ACTION_DOWN:

            //判断单双击

            checkClickDown(ev);

            //

            oldX = ev.getRawX();

            oldY = ev.getRawY();

            mTouchState = (isZooming || isMoving) ? TOUCH_MYSELF : TOUCH_TO_CHILDREN;

            //Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN oldX:" + oldX + ",mTouchState:" + mTouchState);

            break;

        case MotionEvent.ACTION_MOVE:

            // 是否进行了滑动,设置滑动状态

            float tMoveX = ev.getRawX() - oldX;

            final float xDiff = Math.abs(tMoveX);

            float tMoveY = ev.getRawY() - oldY;

            final float yDiff = Math.abs(tMoveY);

            if (yDiff > mTouchSlop || xDiff > mTouchSlop) {

                mTouchState = TOUCH_MYSELF;

            }

            break;

        case MotionEvent.ACTION_CANCEL:

        case MotionEvent.ACTION_UP:

            mTouchState = TOUCH_TO_CHILDREN;

            break;

    }

    // origin do

    return mTouchState != TOUCH_TO_CHILDREN;

}

处理缩放

private void setScaleDetector(){

        ScaleGestureDetector.OnScaleGestureListener scaleListener = new ScaleGestureDetector

                .SimpleOnScaleGestureListener() {

            @Override

            public boolean onScale(ScaleGestureDetector detector) {

                float scaleFactor = detector.getScaleFactor();

                if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))

                    return false;

                //双指缩放中

                isZooming = true;

                mCurrentScale *= scaleFactor;

                if(mCurrentScale < MIN_SCALE){

                    mCurrentScale = MIN_SCALE;

                }

                setScaleX(mCurrentScale);

                setScaleY(mCurrentScale);

                //返回 true 会一闪一闪的

                return false;

            }

            @Override

            public void onScaleEnd(ScaleGestureDetector detector) {

                super.onScaleEnd(detector);

                //Log.i(TAG, "onScaleEnd" );

                scaleEnd();

            }

        };

        mScaleDetector = new ScaleGestureDetector(getContext(), scaleListener);

    }

非缩放时在onTouchEvent()中处理滑动事件

滑动和缩放过程中,处理边界回弹 checkBorder()

通过动画处理各种滑动 animXXX()

 

如何使用

引用

implementation 'com.jagger:SmartTouchLayout:1.0.1'

直接在layout.xml文件中使用,使用方式跟FrameLayout一样

<com.jagger.smartviewlibrary.SmartTouchLayout

        android:id="@+id/stl"

        android:layout_width="match_parent"

        android:layout_height="match_parent" >

        ...

</com.jagger.smartviewlibrary.SmartTouchLayout>

属性设置

设置结束时动画飞到哪去,可指定位置和大小,效果如图1;不设置,则飞到底部如图2;

/**

* 设置结束时,动画回到什么位置和大小

* @param w    view.getWidth()  结束时的宽

* @param h    view.getHeight() 结束时的高

* @param left view location[0] 结束时相对屏幕的X坐标

* @param top  view location[1] 结束时相对屏幕的Y坐标

* @param scaleSide            结束时以宽/高拉伸

*/

public void setEndViewLocalSize(int w, int h, int left, int top, EndViewScaleSide scaleSide)

设置是否需要支持下滑关闭

martTouchLayout.setMoveExitEnable(true);

设置是否需要支持缩放

smartTouchLayout.setZoomEnable(true);

最后要把归属的Activity设置为透明

<!-- AppCompatActivity设置透明主题 -->

<style name="MyTranslucentTheme" parent="Theme.AppCompat.Light.NoActionBar">

        <item name="android:windowNoTitle">true</item>

        <item name="android:windowBackground">@android:color/transparent</item>

        <item name="android:windowIsTranslucent">true</item>

</style>

 

总结,关键是对手势识别和分发,代码中有详细注释,有些地方可能还是处理得不当,请大佬们指点。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值