需求来源:
产品需要实现跟微信朋友圈看大图可下滑退出的效果,
但项目中不仅有大图,图中还有按钮,有文本,有视频等等,总之布局很复杂
反正这些他不管,就是要下滑退出,再缩回上一层界面小图片位置。
找了不少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>
总结,关键是对手势识别和分发,代码中有详细注释,有些地方可能还是处理得不当,请大佬们指点。
本文介绍了一种名为SmartTouchLayout的库,用于在拥有多种元素(图片、按钮、文本、视频)的界面中实现类似微信朋友圈的大图滑动退出效果,同时支持双指缩放和单击/双击操作。关键代码展示了手势识别和事件分发的过程。
934

被折叠的 条评论
为什么被折叠?



