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

简介: 先看效果图 源码Paste_Image.png001.gif002.gif先说一下为什么要写这两个控件DraggingPanelLayout15年的时候,我接到个需求, 做一个仿猿题库的布局,那时候在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>

目录
相关文章
|
机器学习/深度学习 算法 固态存储
最强DETR+YOLO | 三阶段的端到端目标检测器的DEYOv2正式来啦,性能炸裂!!!
最强DETR+YOLO | 三阶段的端到端目标检测器的DEYOv2正式来啦,性能炸裂!!!
999 0
|
9月前
|
存储 缓存 固态存储
2026年阿里云服务器2核8G、4核16G、8核32G最新收费标准和活动价格参考
阿里云服务器提供2核8G、4核16G、8核32G配置,适合中小型数据库、缓存、搜索集群及企业办公等场景。收费标准包括按量付费、包月及多年付费,价格因实例规格和购买时长而异。文中列举了详细价格,如2核8G配置年付701.40元起,4核16G配置年付1291.80元起,8核32G配置年付7551.94元起。用户可根据需求选择,并通过阿里云活动页面获取实时优惠,领券购买可额外减免。
2026年阿里云服务器2核8G、4核16G、8核32G最新收费标准和活动价格参考
|
运维 监控 安全
阿里云SSL证书免费版与收费版有什么区别?免费版SSL证书申请及部署流程参考
在当今数字化时代,网站安全已成为每个网站运营者不可忽视的重要环节。阿里云数字证书管理服务为不同规模的网站提供了多种SSL证书类型和品牌,包括但不限于电商、小型企业、大型企业或个人等。这些SSL证书不仅能有效保护网站数据传输安全,还能提升用户对网站的信任度。本文将详细解析阿里云SSL证书的免费版与收费版之间的差异,并提供选购指南及申请流程,帮助您根据网站规模和业务需求选择最适合的证书。
|
人工智能 搜索推荐 小程序
分享技术---AI智能题库考试系统
本平台融合AI智能技术,打造高效试题库系统,支持PC、手机在线刷题,提供智能出题、自动解析、错题回顾等功能,提升学习效率。具备章节练习、背题模式、笔记收藏等多样化学习方式,支持全终端同步,助力学员精准突破薄弱环节,快速提分。
|
11月前
|
内存技术
观海微电子---常用电子元器件_磁珠
磁珠是一种铁氧体材料制成的高频噪声抑制器件,等效为电阻与电感串联,具有高阻抗特性。其单位为欧姆(Ω),在低频时呈感性,反射噪声;高频时呈阻性,吸收噪声并转化为热能,常用于RF电路、高速信号线等EMI防护,区别于储能用的电感,专用于滤除高频干扰。
|
存储 JSON 缓存
MessagePack - 简介及使用
MessagePack - 简介及使用
2109 1
MessagePack - 简介及使用
|
Dart Java Android开发
Flutter插件开发:开发与使用的技术解析
【4月更文挑战第26天】本文详细介绍了Flutter插件开发,包括创建插件项目、编写原生代码(Android和iOS)、注册与使用插件。通过Flutter命令行工具生成插件,使用MethodChannel进行Dart与原生平台的通信。示例展示了如何获取设备信息并展示在Flutter应用中。文章还提到插件测试与发布,强调了兼容性与性能的重要性,指出Flutter插件是扩展应用功能的关键。
|
安全 Java API
【新手必看】服务端集成网易云信IM 即时通讯
【新手必看】服务端集成网易云信IM 即时通讯
1402 0
|
算法 安全 网络协议
关于国密SSL常用的技术原理介绍
随着越来越多的国际通用密码算法屡屡被传出被破解、被攻击的传闻,存在较高的安全风险。此外,当前我国金融系统大多采用国外制定的加密算法,存在着大量的不可控因素,一旦被不法分子利用攻击,所产生的损失将不可估量。所以国密改造提上日程。国密SSL通信依据的协议是中国人民共和国密码行业标准《SSL VPN技术规范GM/T 0024--2014》协议(链接)。其协议流程和传统的使用RSA证书的TLS协议流程基本一致,但是过程中使用的核心算法已经全部切换到国密相关的算法实现上,为了保证通信的安全监管机构开始推动国内金融行业进行国密改造。我们和客户一起进行了多个国密项目的改造之后,这里整理了国密HTTPS 和国
3036 0
关于国密SSL常用的技术原理介绍
|
JavaScript
蓝易云 - npm install报错问题解决合集
以上是一些常见的npm install错误及其解决方法,希望对你有所帮助。
671 0

热门文章

最新文章