使用ViewDragHelper打造属于自己的DragLayout(抽屉开关 )

2024-06-23 09:04

本文主要是介绍使用ViewDragHelper打造属于自己的DragLayout(抽屉开关 ),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

</com.xujun.drawerLayout.drag.DragLayout>

在代码中若想为其设置监听器,

分别可以监听打开的 时候,关闭的时候,拖动的时候,可以在里面做相应的处理,同时我还加入了 自定义属性可以通过 app:range=”480”或者setRange()方法,即可设置打开抽屉的范围。

mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() {

@Override

public void onOpen() {

Utils.showToast(MainActivity.this, “onOpen”);

// 左面板ListView随机设置一个条目

Random random = new Random();

Log.i(TAG, “onOpen:=” +mDragLayout.getRange());

int nextInt = random.nextInt(50);

mLeftList.smoothScrollToPosition(nextInt);

}

@Override

public void onDraging(float percent) {

Log.d(TAG, "onDraging: " + percent);// 0 -> 1

// 更新图标的透明度

// 1.0 -> 0.0

ViewHelper.setAlpha(mHeaderImage, 1 - percent);

}

@Override

public void onClose() {

Utils.showToast(MainActivity.this, “onClose”);

// 让图标晃动

ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHeaderImage, “translationX”, 15.0f);

mAnim.setInterpolator(new CycleInterpolator(4));

mAnim.setDuration(500);

mAnim.start();

}

});


实现方式


关于ViewDragHelper的一些 讨论

DrawLayout在网上的 实现方式很多,千奇百怪,有一些是直接监听 onTouchEvent事件,处理Activon_Move,Action_Down,Action_up等动作,这样实现的话稍微有点复杂。本篇博客是使用ViewDragHelper来 处理触摸事件和拖拽事件的的,ViewDragHelper是2013Google IO大会推出的,目的是为了给开发者提供一个处理触摸事件,节省开发者的时间。

关于Google官方 关于ViewDragHelper的解释,简单来说就是处理ViewGroup的 触摸事件和拖拽事件

ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state tracking for allowing a user to drag and reposition views within their parent ViewGroup.

实现思路

  • 1) 我是通过继承FrameLayout来实现的,相比较于继承ViewGroup来实现,这样有一个好处就是省去了自己重写 onMeasure (),onLayout ()方法

  • 2)在构造方法里面初始化mDragHelper,mSensitivity代表打开抽屉的 难易程度,是Float类型,至于mCallback是什么,下面会详细讲,这里先不着急。

public DragLayout(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

initAttars(context, attrs);

// a.初始化 (通过静态方法)

mDragHelper = ViewDragHelper.create(this, mSensitivity, mCallback);

}

  • 3)重写 onInterceptTouchEvent和onTouchevent 方法 ,将事件交给

// b.传递触摸事件

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

// 传递给mDragHelper

return mDragHelper.shouldInterceptTouchEvent(ev);

}

/***

  • 将事件交给mDragHelper处理

  • @param event

  • @return

*/

@Override

public boolean onTouchEvent(MotionEvent event) {

try {

mDragHelper.processTouchEvent(event);

} catch (Exception e) {

e.printStackTrace();

}

// 返回true, 持续接受事件

return true;

}

  • 4)重写onFinishInflate方法,在里面拿到 我们的侧滑菜单mLeftContent和主菜单mMainContent

@Override

protected void onFinishInflate() {

super.onFinishInflate();

// 容错性检查 (至少有俩子View, 子View必须是ViewGroup的子类)

if (getChildCount() < 2) {

throw new IllegalStateException("布局至少有俩孩子. Your ViewGroup must have 2 children at " +

“least.”);

}

if (!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)) {

throw new IllegalArgumentException("子View必须是ViewGroup的子类. Your children must be an " +

“instance of ViewGroup”);

}

mLeftContent = (ViewGroup) getChildAt(0);

mMainContent = (ViewGroup) getChildAt(1);

}


下面我们一起来看一下这个mCallBack是什么东西

看之前我们需要了解Status和OnDragStatusChangeListener这两个东西。
  • Status代表DrawLayout 当前的状态,是否是打开,关闭还是拖拽。

  • OnDragStatusChangeListener是个监听器,在DrawLayout状态改变的时候会回调相关的方法,方便与外界进行通讯。

  • 我们可以通过 setDragStatusListener(OnDragStatusChangeListener mListener);这个方法设置监听

/**

  • 状态枚举

*/

public static enum Status {

Close, Open, Draging;

}

/**

  • 抽屉开关的监听器

*/

public interface OnDragStatusChangeListener {

void onClose();

void onOpen();

void onDraging(float percent);

}

接下来我们来看ViewDragHelper.Callback几个主要的方法


tryCaptureView(View child, int pointerId)

Called when the user’s input indicates that they want to capture the given child view with the pointer indicated by pointerId.

onViewCaptured(View capturedChild, int activePointerId)

Called when a child view is captured for dragging or settling.

getViewHorizontalDragRange(View child)

Return the magnitude of a draggable child view’s horizontal range of motion in pixels.

clampViewPositionHorizontal(View child, int left, int dx)

Restrict the motion of the dragged child view along the horizontal axis.

onViewPositionChanged(View changedView, int left, int top, int dx, int dy)

Called when the captured view’s position changes as the result of a drag or settle.

onViewReleased(View releasedChild, float xvel, float yvel)

Called when the child view is no longer being actively dragged.

谷歌官方的连接;https://developer.android.com/reference/android/support/v4/widget/ViewDragHelper.Callback.html


下面的代码有关于这几个方法的中文解释,这里就不详细讲解了

ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {

// d. 重写事件

// 1. 根据返回结果决定当前child是否可以拖拽

// child 当前被拖拽的View

// pointerId 区分多点触摸的id

@Override

public boolean tryCaptureView(View child, int pointerId) {

Log.d(TAG, "tryCaptureView: " + child);

return mToogle;

}

@Override

public void onViewCaptured(View capturedChild, int activePointerId) {

Log.d(TAG, "onViewCaptured: " + capturedChild);

// 当capturedChild被捕获时,调用.

super.onViewCaptured(capturedChild, activePointerId);

}

@Override

public int getViewHorizontalDragRange(View child) {

// 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度

Log.i(TAG, “getViewHorizontalDragRange:mRange=” +mRange);

return mRange;

}

// 2. 根据建议值 修正将要移动到的(横向)位置 (重要)

// 此时没有发生真正的移动

public int clampViewPositionHorizontal(View child, int left, int dx) {

// child: 当前拖拽的View

// left 新的位置的建议值, dx 位置变化量

// left = oldLeft + dx;

Log.d(TAG, "clampViewPositionHorizontal: "

  • "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " + left);

if (child == mMainContent) {

left = fixLeft(left);

}

return left;

}

// 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)

// 此时,View已经发生了位置的改变

@Override

public void onViewPositionChanged(View c​
hangedView, int left, int top,

int dx, int dy) {

// changedView 改变位置的View

// left 新的左边值

// dx 水平方向变化量

super.onViewPositionChanged(changedView, left, top, dx, dy);

Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);

int newLeft = left;

if (changedView == mLeftContent) {

// 把当前变化量传递给mMainContent

newLeft = mMainContent.getLeft() + dx;

}

// 进行修正

newLeft = fixLeft(newLeft);

if (changedView == mLeftContent) {

// 当左面板移动之后, 再强制放回去.

mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);

mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);

}

// 更新状态,执行动画

dispatchDragEvent(newLeft);

// 为了兼容低版本, 每次修改值之后, 进行重绘

invalidate();

}

// 4. 当View被释放的时候, 处理的事情(执行动画)

@Override

public void onViewReleased(View releasedChild, float xvel, float yvel) {

// View releasedChild 被释放的子View

// float xvel 水平方向的速度, 向右为+

// float yvel 竖直方向的速度, 向下为+

Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel);

super.onViewReleased(releasedChild, xvel, yvel);

// 判断执行 关闭/开启

// 先考虑所有开启的情况,剩下的就都是关闭的情况

if (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f) {

open();

} else if (xvel > 0) {

open();

} else {

close();

}

}

@Override

public void onViewDragStateChanged(int state) {

// TODO Auto-generated method stub

super.onViewDragStateChanged(state);

}

};

其实主要思路就是

  • 1)在方法public boolean tryCaptureView(View child, int pointerId)处理那些child可以被捕捉,这里我们返回true表示所有的都可以被捕捉

  • 2)在public int clampViewPositionHorizontal(View child, int left, int dx)方法中根据child返回将要移动的水平位置的偏移量

  • 3)在 void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)方法中处理要做的事情 包括更新状态, 伴随动画, 重绘界面等

public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {

// 进行修正

newLeft = fixLeft(newLeft);

if (changedView == mLeftContent) {

// 当左面板移动之后, 再强制放回去.

mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);

mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);

if (changedView == mLeftContent) {

// 把当前变化量传递给mMainContent

newLeft = mMainContent.getLeft() + dx;

}

}

// 更新状态,执行动画

dispatchDragEvent(newLeft);

// 为了兼容低版本, 每次修改值之后, 进行重绘

invalidate();

}

protected void dispatchDragEvent(int newLeft) {

float percent = newLeft * 1.0f / mRange;

//0.0f -> 1.0f

Log.d(TAG, "percent: " + percent);

if (mListener != null) {

mListener.onDraging(percent);

}

// 更新状态, 执行回调

Status preStatus = mStatus;

mStatus = updateStatus(percent);

if (mStatus != preStatus) {

// 状态发生变化

if (mStatus == Status.Close) {

// 当前变为关闭状态

if (mListener != null) {

mListener.onClose();

}

} else if (mStatus == Status.Open) {

if (mListener != null) {

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
" + percent);

if (mListener != null) {

mListener.onDraging(percent);

}

// 更新状态, 执行回调

Status preStatus = mStatus;

mStatus = updateStatus(percent);

if (mStatus != preStatus) {

// 状态发生变化

if (mStatus == Status.Close) {

// 当前变为关闭状态

if (mListener != null) {

mListener.onClose();

}

} else if (mStatus == Status.Open) {

if (mListener != null) {

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-ltiQLwvx-1719098040066)]一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

这篇关于使用ViewDragHelper打造属于自己的DragLayout(抽屉开关 )的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1086719

相关文章

Python使用Pandas对比两列数据取最大值的五种方法

《Python使用Pandas对比两列数据取最大值的五种方法》本文主要介绍使用Pandas对比两列数据取最大值的五种方法,包括使用max方法、apply方法结合lambda函数、函数、clip方法、w... 目录引言一、使用max方法二、使用apply方法结合lambda函数三、使用np.maximum函数

Qt 中集成mqtt协议的使用方法

《Qt中集成mqtt协议的使用方法》文章介绍了如何在工程中引入qmqtt库,并通过声明一个单例类来暴露订阅到的主题数据,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一,引入qmqtt 库二,使用一,引入qmqtt 库我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本