Android仿新版微信的小程序下拉栏

2023-12-18 19:08

本文主要是介绍Android仿新版微信的小程序下拉栏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作者:大头呆
链接:https://juejin.im/post/5a4c90c15188257c4d1b8d0c 

本文经作者授权推送。

上周微信更新到了6.6.1版本,加入了微信小游戏。朋友圈都在玩跳一跳。而且现在微信把最近用过的小程序放到了首页顶部,轻轻下拉就可以快速访问了。可以看下效果,如果还没升级的朋友可要抓紧了。

自己作为一个安卓程序员,虽然不会写小程序,但也要紧跟热潮(蹭热点)啊。于是乎就干脆仿写下这个下拉控件吧。第七宇宙惯例,先上效果图:

上图的主界面是我上一篇文章《撸一款全手势操作浏览器》写的demo。写完这个控件,发现正好可以作为它的下拉菜单栏,就直接用上了。好了,废话不多说,开始介绍下实现流程。

流程分析

整个下拉过程分为四个阶段:

  • 阶段一:出现一个圆点,半径随下拉距离变大而变大。位置始终在中间

  • 阶段二:圆点两边出现两个圆点,半径较小。距离随下拉距离变大而变大,中间圆点半径不断变小。位置始终在中间

  • 阶段三:从顶部出现内容列表,位置随手指下拉快速往下移动,同时三个圆点位置不断下移并逐渐消失

  • 阶段四:只剩下内容列表,手指可以继续往下滑动,但阻尼变大。内容列表始终在中间。

上滑分两种情况:

  • 如果开始上滑的时候内容列表已展开,则平移上滑(圆点不会出现)

  • 反之,就是下拉的逆过程了(圆点会出现)。

具体实现

熟悉下拉刷新控件的同学可以看出来,上述滑动的流程和下拉刷新很相似,所以为了避免重复造轮子(偷懒),我将下拉刷新控件作了改动,所以主要的实现还是在头部那块。

初始布局位置

将头部放到屏幕外层的方法有很多。我采用了设置负数padding的方法。外层布局继承了LinearLayout,方向竖直。然后为其设置padding:

headerHeight = (null != mHeaderLayout) ? mHeaderLayout.getMeasuredHeight() : 0;
int pLeft = getPaddingLeft();
int pTop = -headerHeight;
int pRight = getPaddingRight();
int pBottom = -footerHeight;
setPadding(pLeft, pTop, pRight, pBottom);

paddingTop的值等于负的HeaderLayout的高度,这样正好将头部布局顶到屏幕外面。

处理触摸事件
这块主要内容就是重写写 boolean onInterceptTouchEvent(MotionEvent event)boolean onTouchEvent(MotionEvent ev)来拦截和处理滑动事件。

@Overridepublic final boolean onInterceptTouchEvent(MotionEvent event) {final int action = event.getAction();//不拦截if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {mIsHandledTouchEvent = false;return false;}//如果不是重新开始触摸且已经判断需要拦截,就一直拦截整套触摸事件if (action != MotionEvent.ACTION_DOWN && mIsHandledTouchEvent) {return true;}switch (action) {case MotionEvent.ACTION_DOWN:mLastMotionY = event.getY();mIsHandledTouchEvent = false;break;case MotionEvent.ACTION_MOVE:final float deltaY = event.getY() - mLastMotionY;final float absDiff = Math.abs(deltaY);// 位移差大于mTouchSlop(TouchSlop是系统所能识别出的被认为是滑动的最小距离)//这是为了防止快速拖动引发刷新if ((absDiff > mTouchSlop)) {mLastMotionY = event.getY();// 第一个显示出来,Header已经显示或拉下if (isPullRefreshEnabled() && isReadyForPullDown()) {// 1,Math.abs(getScrollY()) > 0:表示当前滑动的偏移量的绝对值大于0,表示当前HeaderView滑出来了或完全// 不可见,存在这样一种case,当正在刷新时并且RefreshableView已经滑到顶部,向上滑动,那么我们期望的结果是// 依然能向上滑动,直到HeaderView完全不可见// 2,deltaY > 0.5f:表示下拉的值大于0.5fmIsHandledTouchEvent = (Math.abs(getScrollYValue()) > 0 || deltaY > 0.5f);}}break;default:break;}return mIsHandledTouchEvent;//true:拦截,false不拦截}

如果拦截了,我们处理滑动.其中offsetRadio是滑动阻尼值。

@Overridepublic final boolean onTouchEvent(MotionEvent ev) {boolean handled = false;switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:mLastMotionY = ev.getY();mIsHandledTouchEvent = false;break;case MotionEvent.ACTION_MOVE:final float deltaY = ev.getY() - mLastMotionY;mLastMotionY = ev.getY();if (isPullRefreshEnabled() && isReadyForPullDown()) {pullHeaderLayout(deltaY / offsetRadio);handled = true;} else {mIsHandledTouchEvent = false;}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:if (mIsHandledTouchEvent) {mIsHandledTouchEvent = false;// 当第一个显示出来时if (isReadyForPullDown()) {// 调用刷新if (mPullRefreshEnabled && (mPullDownState == State.RELEASE_TO_REFRESH)) {startRefreshing();handled = true;}resetHeaderLayout();} }break;default:break;}return handled;}

最终我们会通过 pullHeaderLayout()调用View的scrollBy(x, y)方法将布局整体滚动:

protected void pullHeaderLayout(float delta) {// 向上滑动,并且当前scrollY为0时,不滑动int oldScrollY = getScrollYValue();if (delta < 0 && (oldScrollY - delta) >= 0) {setScrollTo(0, 0);if (null != mHeaderLayout && 0 != mHeaderHeight) {mHeaderLayout.setState(State.RESET);mHeaderLayout.onPull(0);}return;}//滑动布局setScrollBy(0, -(int) delta);//调用View的 scrollBy(x, y)int scrollY = Math.abs(getScrollYValue());if (null != mHeaderLayout && 0 != mHeaderHeight) {if (scrollY >= headerListHeight) {mHeaderLayout.setState(State.arrivedListHeight);setOffsetRadio(2.0f);//内容列表完全展开后阻尼值变大} else {setOffsetRadio(1.0f);}mHeaderLayout.onPull(scrollY);//将滑动距离实时传给头部,以实现出我们需要的动画}}

遇到的一个问题

我在上面这样写好之后,跑了一遍,发现 onInterceptTouchEvent只执行了ACTION_DOWN,后续的ACTION_MOVEACTION_UP事件不会执行,也就无法拦截和进行滑动了。百度下原来有这么一个规则:

onInterceptTouchEvent返回false表示将down事件交由子View来处理;若某一层子View的onTouchEvent返回了true,后续的move、up等事件都将先传递到ViewGrouponInterceptTouchEvent的方法,并继续层层传递下去,交由子View处理;若子ViewonTouchEvent都返回了false,则down事件将交由该ViewGrouponTouchEvent来处理;如果ViewGrouponTouchEvent返回true,后续事件不再经过该ViewGrouponInterceptTouchEvent方法,直接传递给onTouchEvent方法处理。

因为目前的子View(中间内容部分)是RelativeLayout,它的onTouchEvent默认返回了falseListView等其他可滑动的控件不会有这个问题)。解决办法是设置android:clickable="true"。

头部的实现

上面我说过滑动有四个阶段,只要将滑动距离传递给自定义头部,根据距离判断状态,实时改变内容列表的TranslationY,圆点的TranslationYAlpha就可以了。实现起来虽然内容比较多,但都比较简单,详细的代码就不贴了,大家感兴趣的可以去看源码。至于圆点动画,也是一个自定义View,外层只要根据滑动距离换算下动画的百分比传进去,在里面画出需要的图形就行了,判断当百分比到0.5的时候画三个圆 :

public class ExpendPoint extends View {float percent;float maxRadius = 15;float maxDist = 60;Paint mPaint;public ExpendPoint(Context context, @Nullable AttributeSet attrs) {super(context, attrs);mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setColor(Color.GRAY);}public void setPercent(float percent) {this.percent = percent;invalidate();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);float centerX = getWidth() / 2;float centerY = getHeight() / 2;if (percent <= 0.5f) {只画一个圆mPaint.setAlpha(255);float radius = percent * 2 * maxRadius;canvas.drawCircle(centerX, centerY, radius, mPaint);} else {//画三个个圆float afterPercent = (percent - 0.5f) / 0.5f;float radius = maxRadius - maxRadius / 2 * afterPercent;canvas.drawCircle(centerX, centerY, radius, mPaint);canvas.drawCircle(centerX - afterPercent * maxDist, centerY, maxRadius / 2, mPaint);canvas.drawCircle(centerX + afterPercent * maxDist, centerY, maxRadius / 2, mPaint);}}
}

拓展:上拉栏

有了上面的基础,实现上拉加载栏也是很简单的,逻辑基本相同,只是方向变了而已。我们来看下最终效果:

最后贴下本项目github地址:

https://github.com/renjianan/SimpleBrowser

往期干货

1

Android 弧形ViewPager 和弧形HeaderView(升级版)

2

5道谷歌面试题,你能答对几道?

3

回顾2017系列篇(二):移动端APP设计趋势

这篇关于Android仿新版微信的小程序下拉栏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk