DraggerViewHalper-任意摆放-底部拖动布局-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

DraggerViewHalper-任意摆放-底部拖动布局

简介: 先看效果图 源码 Paste_Image.png 001.gif 002.gif 先说一下为什么要写这两个控件 DraggingPanelLayout 15年的时候,我接到个需求, 做一个仿猿题库的布局,那时候在github上搜了很多SlidingUpLayout相关的布局,基本上是满足了需求,后来因为iOS的搭档没找到相关的控件,我们放弃了。

先看效果图 源码

Paste_Image.png
001.gif
002.gif

先说一下为什么要写这两个控件

  • DraggingPanelLayout

15年的时候,我接到个需求, 做一个仿猿题库的布局,那时候在github上搜了很多SlidingUpLayout相关的布局,基本上是满足了需求,后来因为iOS的搭档没找到相关的控件,我们放弃了。

  • FreeLayout

16年的时候,我接到一个需求,做一个任意摆放的View,那时候是用原生的onTouchEvent去实现的,现在学会了DraggerViewHelper,就顺便再写一次。

FreeLayout

<pre>
package org.alex.freelayout;

import android.content.Context;
import android.support.annotation.FloatRange;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;

/**
* 作者:Alex
* 时间:2016/11/11 16:16
* 简述:
* 子控件加上 android:tag="false" 表示不可以拖动
* 子控件加上 android:tag="true" 表示可任意拖动
*/
public class FreeLayout extends RelativeLayout {
private ViewDragHelper viewDragHelper;
private int width;
private int height;

public FreeLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView();
}

private void initView() {
    ViewDragHelperCallback dragHelperCallback = new ViewDragHelperCallback();
    viewDragHelper = ViewDragHelper.create(this, 1.0F, dragHelperCallback);
    /\*跟踪左边界拖动\*/
    viewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return viewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    viewDragHelper.processTouchEvent(event);
    return true;
}

private final class ViewDragHelperCallback extends ViewDragHelper.Callback {

    /\*\*
     \* 手指释放的时候回调
     \*/
    public void onViewReleased(final View releasedChild, float xvel, float yvel) {
        release4BackBorder(releasedChild);
    }

    /\*\*
     \* 当captureview的位置发生改变时回调
     \*/
    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        super.onViewPositionChanged(changedView, left, top, dx, dy);

    }


    /\*\*
     \* true的时候会锁住当前的边界,false则unLock。
     \*/
    public boolean onEdgeLock(int edgeFlags) {
        return false;
    }

    public int getViewHorizontalDragRange(View child) {
        return getMeasuredWidth() - child.getMeasuredWidth();
    }

    /\*\*
     \* 尝试捕获子view, 返回true表示允许。
     \*
     \* @param child     尝试捕获的view
     \* @param pointerId 指示器id?
     \*/
    public boolean tryCaptureView(View child, int pointerId) {
        return "true".equals(child.getTag());
    }

    /\*\*
     \* 处理水平方向上的拖动,返回值就是最终确定的移动的位置。
     \* 实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。
     \* 除此之外就是如果你的layout设置了padding的话,
     \* 也可以让子view的活动范围在padding之内的.
     \*
     \* @param child 被拖动到view
     \* @param left  移动到达的x轴的距离
     \* @param dx    建议的移动的x距离
     \*/
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        return left;
    }


    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return top;
    }
}

/\*\*
 \* 回到 边沿处
 \*/
private void release4BackBorder(View releasedChild) {
    int left = releasedChild.getLeft();
    int right = releasedChild.getRight();
    int top = releasedChild.getTop();
    int bottom = releasedChild.getBottom();
    if ((top >= 0) && (left >= 0) && (right <= width) && (bottom <= height)) {
        return;
    }
    int finalLeft = width - releasedChild.getWidth();
    int finalTop = height - releasedChild.getHeight();
    /\*
    \* 实际情况证明,这里必须是区分 8 种情况,不能是4种情况,
    \* 否则在 飞速滑动的时候,子控件飞出左上角、右上角、左下角、右下角,
    \* 不会被拉回来的
    \* \*/
    if ((top >= 0) && (left < 0) && (right <= width) && (bottom <= height)) {
        /\*左\*/
        viewDragHelper.smoothSlideViewTo(releasedChild, 0, top);
    } else if ((top < 0) && (left > 0) && (right <= width) && (bottom <= height)) {
        /\*上\*/
        viewDragHelper.smoothSlideViewTo(releasedChild, left, 0);
    } else if ((top >= 0) && (left >= 0) && (right > width) && (bottom <= height)) {
        /\*右\*/
        viewDragHelper.smoothSlideViewTo(releasedChild, finalLeft, top);
    } else if ((top >= 0) && (left >= 0) && (right <= width) && (bottom > height)) {
        /\*下\*/
        viewDragHelper.smoothSlideViewTo(releasedChild, left, finalTop);
    } else if ((top < 0) && (left < 0) && (right <= width) && (bottom <= height)) {
        /\*左上\*/
        viewDragHelper.smoothSlideViewTo(releasedChild, 0, 0);
    } else if ((top < 0) && (left >= 0) && (right > width) && (bottom <= height)) {
        /\*右上\*/
        viewDragHelper.smoothSlideViewTo(releasedChild, finalLeft, 0);
    } else if ((top >= 0) && (left < 0) && (right <= width) && (bottom > height)) {
        /\*左下\*/
        viewDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop);
    } else if ((top >= 0) && (left >= 0) && (right > width) && (bottom > height)) {
        /\*右下\*/
        viewDragHelper.smoothSlideViewTo(releasedChild, finalLeft, finalTop);
    }
    ViewCompat.postInvalidateOnAnimation(FreeLayout.this);
}

@Override
public void computeScroll() {
    if (viewDragHelper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    width = w;
    height = h;
}

/\*\*
 \* 数据转换: dp---->px
 \*/
public float dp2px(@FloatRange(from = 0) float dp) {
    return dp \* getResources().getDisplayMetrics().density;
}

/\*\*
 \* sp转px
 \*/
public int sp2px(@FloatRange(from = 0) float sp) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
}

}

</pre>

DraggingPanelLayout

<pre>
package org.alex.draggingpanellayout;

import android.content.Context;
import android.support.annotation.FloatRange;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;

import org.alex.library.R;

/**
* 作者:Alex
* 时间:2016年11月12日
* 简述:
*/

public class DraggingPanelLayout extends RelativeLayout {
private View draggerView;
private int draggerViewHeight;
private ViewDragHelper viewDragHelper;
/* 距离顶部最小的距离*/
private static int minMarginTop;

public DraggingPanelLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView(context);
}

private void initView(Context context) {
    minMarginTop = (int) dp2px(32);
    ViewDragHelperCallback dragHelperCallback = new ViewDragHelperCallback();
    viewDragHelper = ViewDragHelper.create(this, 1.0F, dragHelperCallback);
    /\*跟踪左边界拖动\*/
    viewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return viewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    viewDragHelper.processTouchEvent(event);
    return true;
}

private final class ViewDragHelperCallback extends ViewDragHelper.Callback {

    /\*\*
     \* 手指释放的时候回调
     \*/
    public void onViewReleased(final View releasedChild, float xvel, float yvel) {

    }

    /\*\*
     \* 当captureview的位置发生改变时回调
     \*/
    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        super.onViewPositionChanged(changedView, left, top, dx, dy);

        /\*往上拖动 dy 为负\*/
        RelativeLayout.LayoutParams draggerViewLayoutParams = (RelativeLayout.LayoutParams) draggerView.getLayoutParams();
        draggerViewLayoutParams.topMargin = draggerViewLayoutParams.topMargin + dy;
        draggerView.setLayoutParams(draggerViewLayoutParams);

    }


    /\*\*
     \* true的时候会锁住当前的边界,false则unLock。
     \*/
    public boolean onEdgeLock(int edgeFlags) {
        return false;
    }

    public int getViewHorizontalDragRange(View child) {
        return getMeasuredWidth() - child.getMeasuredWidth();
    }

    /\*\*
     \* 尝试捕获子view, 返回true表示允许。
     \*
     \* @param child     尝试捕获的view
     \* @param pointerId 指示器id?
     \*/
    public boolean tryCaptureView(View child, int pointerId) {
        return child == draggerView;
    }

    @Override
    public int getViewVerticalDragRange(View child) {
        return getMeasuredHeight() - child.getMeasuredHeight();
    }

    /\*\*
     \* 处理水平方向上的拖动,返回值就是最终确定的移动的位置。
     \* 实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。
     \* 除此之外就是如果你的layout设置了padding的话,
     \* 也可以让子view的活动范围在padding之内的.
     \*
     \* @param child 被拖动到view
     \* @param left  移动到达的x轴的距离
     \* @param dx    建议的移动的x距离
     \*/
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        return left;
    }


    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        if (top > getHeight() - draggerViewHeight) {
            /\*底部边界\*/
            top = getHeight() - draggerViewHeight;
        } else if (top < minMarginTop) {
            /\*顶部边界\*/
            top = minMarginTop;
        }
        return top;
    }
}

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    draggerView = findViewById(R.id.dpl_dragger);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    draggerViewHeight = draggerView.getMeasuredHeight();
}

/\*\*
 \* 数据转换: dp---->px
 \*/
public float dp2px(@FloatRange(from = 0) float dp) {
    return dp \* getResources().getDisplayMetrics().density;
}

}

</pre>

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章