SwipeToLoadLayout布局中添加自定义控件仿美团、饿了吗等下拉效果

2024-05-12 04:18

本文主要是介绍SwipeToLoadLayout布局中添加自定义控件仿美团、饿了吗等下拉效果,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  SwipeToLoadLayout是一个可重用的下拉刷新和上拉加载控件,理论上支持各种View和ViewGroup(ListView,ScrollView,RecyclerView,GridView,WebView,Linearlayout,RelativeLayout,FrameLayout,ImageView,TextView等等)的刷新和加载,还支持自动刷新,手动刷新,自动加载,手动加载,禁止刷新,禁止加载等操作,同时也可以自定义头部和尾部,头部还分classic,above,blow,scale四种类型,还有自动刷新的效果,体验也很流畅。
  这里我们使用它来实现我们所常见的几种下拉刷新效果,如百度外卖、饿了吗、京东商城、美团外卖、天猫、微博及天气的下拉刷新效果;
  第一步:首先在build.gradle(Project:项目名称)中的repositories下面添加JitPack代码库,如下:

    repositories {......maven { url "https://jitpack.io" }}

  第二步:在build.gradle(Module:app)中添加依赖项,如下:

    dependencies {compile 'com.github.Aspsine:SwipeToLoadLayout:1.0.4'
}

  第三步:接下来自定义头部刷新控件,这里笔者先自定义了一个饿了吗的下拉刷新控件,自定义头部刷新控件需要继承自SwipeRefreshHeaderLayout,代码如下:

public class ELeMaRefreshHeaderView extends SwipeRefreshHeaderLayout {private int mHeaderHeight;private boolean rotated = false;// 创建Handler发送延迟执行的指令private Handler mHandler = new Handler();private ImageView imageViewDrumstick;private ImageView imageViewHotPot;private ImageView imageViewRice;private ImageView imageViewVegetable;private RelativeLayout imageViewPotCover;public ELeMaRefreshHeaderView(Context context) {this(context, null);}public ELeMaRefreshHeaderView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public ELeMaRefreshHeaderView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mHeaderHeight = getResources().getDimensionPixelOffset(R.dimen.refresh_header_height_cook);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();imageViewDrumstick = (ImageView) findViewById(R.id.image_view_drumstick);imageViewHotPot = (ImageView) findViewById(R.id.image_view_hot_pot);imageViewRice = (ImageView) findViewById(R.id.image_view_rice);imageViewVegetable = (ImageView) findViewById(R.id.image_view_vegetable);imageViewPotCover = (RelativeLayout) findViewById(R.id.image_view_pot_cover);}@Overridepublic void onRefresh() {//正在刷新,执行动画imageViewPotCover.setVisibility(View.INVISIBLE);imageViewDrumstick.setVisibility(View.VISIBLE);imageViewHotPot.setVisibility(View.VISIBLE);imageViewRice.setVisibility(View.VISIBLE);imageViewVegetable.setVisibility(View.VISIBLE);imageViewPotCover.setAlpha(0);startAnimation();}@Overridepublic void onPrepare() {}@Overridepublic void onMove(int y, boolean isComplete, boolean automatic) {if (!isComplete) {if (y >= mHeaderHeight) {if (!rotated) {rotated = true;}} else if (y < mHeaderHeight) {float tan = (float) (y - mHeaderHeight * 2 / 3) / (float) (imageViewPotCover.getWidth());int angle = (int) (tan * 90);if (angle > 30) {angle = 30;}if (angle < 0) {angle = 0;}imageViewPotCover.setRotation(-angle);}}}@Overridepublic void onRelease() {Log.d("CookRefreshHeaderView", "onRelease()");}@Overridepublic void onComplete() {rotated = false;mHandler.removeCallbacksAndMessages(null);imageViewPotCover.setVisibility(View.VISIBLE);imageViewDrumstick.setVisibility(View.GONE);imageViewHotPot.setVisibility(View.GONE);imageViewRice.setVisibility(View.GONE);imageViewVegetable.setVisibility(View.GONE);imageViewPotCover.setAlpha(1);imageViewPotCover.setRotation(0);float[] defaultPoint = {0, 0};startParabolaAnimation(imageViewDrumstick, defaultPoint, defaultPoint, defaultPoint);startParabolaAnimation(imageViewHotPot, defaultPoint, defaultPoint, defaultPoint);startParabolaAnimation(imageViewRice, defaultPoint, defaultPoint, defaultPoint);startParabolaAnimation(imageViewVegetable, defaultPoint, defaultPoint, defaultPoint);}@Overridepublic void onReset() {rotated = false;}private void startAnimation() {float x = 0;float y = 0;final float[] startPoint = {x, y};Random random = new Random();int nextInt = random.nextInt(50);final float[] endPoint = {nextInt + 180, nextInt + 30};final float[] midPoint = {nextInt + 100, nextInt - 70};nextInt = random.nextInt(40);final float[] endPoint2 = {nextInt + 160, nextInt + 40};final float[] midPoint2 = {nextInt + 80, nextInt - 80};nextInt = random.nextInt(30);final float[] endPoint3 = {nextInt - 200, nextInt + 40};final float[] midPoint3 = {nextInt - 100, nextInt - 70};nextInt = random.nextInt(60);final float[] endPoint4 = {nextInt - 170, nextInt + 45};final float[] midPoint4 = {nextInt - 80, nextInt - 80};mHandler.postDelayed(new Runnable() {@Overridepublic void run() {startParabolaAnimation(imageViewDrumstick, startPoint, endPoint, midPoint);}}, 100);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {startParabolaAnimation(imageViewHotPot, startPoint, endPoint2, midPoint2);}}, 200);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {startParabolaAnimation(imageViewRice, startPoint, endPoint3, midPoint3);}}, 300);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {startParabolaAnimation(imageViewVegetable, startPoint, endPoint4, midPoint4);}}, 400);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {startAnimation();}}, 500);}/*** 抛物线动画** @param view* @param startPoint 起点坐标* @param endPoint   结束点坐标* @param midPoint   中间点坐标* @return*/public static ObjectAnimator startParabolaAnimation(final View view, float[] startPoint, float[] endPoint, float[] midPoint) {//分200帧完成动画int count = 200;//动画时间持续600毫秒int duration = 600;Keyframe[] keyframes = new Keyframe[count];final float keyStep = 1f / (float) count;float key = keyStep;//计算并保存每一帧x轴的位置for (int i = 0; i < count; ++i) {keyframes[i] = Keyframe.ofFloat(key, i * getDx(startPoint, endPoint) / count + startPoint[0]);key += keyStep;}PropertyValuesHolder pvhX = PropertyValuesHolder.ofKeyframe("translationX", keyframes);key = keyStep;//计算并保存每一帧y轴的位置for (int i = 0; i < count; ++i) {keyframes[i] = Keyframe.ofFloat(key, getY(startPoint, endPoint, midPoint, i * getDx(startPoint, endPoint) / count + startPoint[0]));key += keyStep;}PropertyValuesHolder pvhY = PropertyValuesHolder.ofKeyframe("translationY", keyframes);ObjectAnimator yxBouncer = ObjectAnimator.ofPropertyValuesHolder(view, pvhY, pvhX).setDuration(duration);//开始动画yxBouncer.start();return yxBouncer;}private static float getDx(float[] startPoint, float[] endPoint) {return endPoint[0] - startPoint[0];}private static float getDy(float[] startPoint, float[] endPoint) {return endPoint[1] - startPoint[1];}/*** 这里是根据三个坐标点{startPoint,endPoint,midPoint}计算出来的抛物线方程* y = ax² + bx + c** @param x* @return y*/private static float getY(float[] startPoint, float[] endPoint, float[] midPoint, float x) {float x1 = startPoint[0];float y1 = startPoint[1];float x2 = endPoint[0];float y2 = endPoint[1];float x3 = midPoint[0];float y3 = midPoint[1];float a, b, c;a = (y1 * (x2 - x3) + y2 * (x3 - x1) + y3 * (x1 - x2))/ (x1 * x1 * (x2 - x3) + x2 * x2 * (x3 - x1) + x3 * x3 * (x1 - x2));b = (y1 - y2) / (x1 - x2) - a * (x1 + x2);c = y1 - (x1 * x1) * a - x1 * b;return a * x * x + b * x + c;}
}

  第四步:布局文件中使用我们自定义控件,如下:

<neu.cn.swiperefreshdemo.customview.ELeMaRefreshHeaderView
    xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><RelativeLayout
        android:layout_width="match_parent"android:layout_height="90dp"><RelativeLayout
            android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center_horizontal|bottom"><ImageView
                android:id="@+id/image_view_drumstick"android:layout_width="30dp"android:layout_height="30dp"android:layout_marginBottom="24dp"android:src="@drawable/icon_cook_01" /><ImageView
                android:id="@+id/image_view_hot_pot"android:layout_width="30dp"android:layout_height="30dp"android:layout_marginBottom="24dp"android:src="@drawable/icon_cook_02" /><ImageView
                android:id="@+id/image_view_rice"android:layout_width="30dp"android:layout_height="30dp"android:layout_marginBottom="24dp"android:src="@drawable/icon_cook_03" /><ImageView
                android:id="@+id/image_view_vegetable"android:layout_width="30dp"android:layout_height="30dp"android:layout_marginBottom="24dp"android:src="@drawable/icon_cook_04" /></RelativeLayout><LinearLayout
            android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:gravity="center"android:orientation="vertical"><RelativeLayout
                android:id="@+id/image_view_pot_cover"android:layout_width="50dp"android:layout_height="30dp"android:rotation="-0"android:transformPivotX="0dp"android:transformPivotY="30dp"><ImageView
                    android:layout_width="50dp"android:layout_height="18dp"android:layout_alignParentBottom="true"android:layout_centerHorizontal="true"android:scaleType="fitXY"android:src="@drawable/icon_cook_pan_cover" /></RelativeLayout><RelativeLayout
                android:layout_width="60dp"android:layout_height="30dp"><ImageView
                    android:id="@+id/image_view_pot"android:layout_width="60dp"android:layout_height="match_parent"android:layout_centerInParent="true"android:scaleType="fitXY"android:src="@drawable/icon_cook_pan" /><ImageView
                    android:layout_width="16dp"android:layout_height="16dp"android:layout_alignParentBottom="true"android:layout_centerHorizontal="true"android:layout_marginBottom="10dp"android:src="@drawable/icon_cook_03" /><ImageView
                    android:layout_width="20dp"android:layout_height="20dp"android:layout_alignParentBottom="true"android:layout_marginBottom="4dp"android:layout_marginLeft="10dp"android:src="@drawable/icon_cook_04" /><ImageView
                    android:layout_width="16dp"android:layout_height="16dp"android:layout_alignParentBottom="true"android:layout_alignParentRight="true"android:layout_marginBottom="4dp"android:layout_marginRight="10dp"android:src="@drawable/icon_cook_01" /><ImageView
                    android:layout_width="46dp"android:layout_height="28dp"android:layout_centerInParent="true"android:scaleType="fitXY"android:src="@drawable/icon_cook_water" /></RelativeLayout></LinearLayout></RelativeLayout></neu.cn.swiperefreshdemo.customview.ELeMaRefreshHeaderView>

  这里笔者为了减少布局的嵌套,使用了引入布局,在fragment_elema.xml中引入上面的布局文件,如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/colorPrimary"><com.aspsine.swipetoloadlayout.SwipeToLoadLayoutandroid:id="@+id/swipe_to_loadlayout"android:layout_width="match_parent"android:layout_height="match_parent"><includeandroid:id="@id/swipe_refresh_header"layout="@layout/layout_elema_header" /><TextViewandroid:id="@id/swipe_target"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#1E90FF"android:gravity="center"android:text="下拉刷新"android:textSize="24sp" /></com.aspsine.swipetoloadlayout.SwipeToLoadLayout></RelativeLayout>

注意: SwipeToLoadLayout中布局包裹的view的id是指定的,笔者也不懂为何要指定,不要乱改就好啦!否者会找不到的;

<item name="swipe_target" type="id" />  刷新目标
<item name="swipe_refresh_header" type="id" />  刷新头部
<item name="swipe_load_more_footer" type="id" />  刷新尾部

  第五步:代码中加载fragment布局文件,并为SwipeToLoadLayout设置下拉刷新监听事件;

public class ELeMaRefreshFragment extends Fragment implements OnRefreshListener {private SwipeToLoadLayout mSwipeToLoadLayout;@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View mFragmentView = inflater.inflate(R.layout.fragment_elema, container, false);mSwipeToLoadLayout = (SwipeToLoadLayout) mFragmentView.findViewById(R.id.swipe_to_loadlayout);mSwipeToLoadLayout.setOnRefreshListener(this);mSwipeToLoadLayout.postDelayed(new Runnable() {@Overridepublic void run() {mSwipeToLoadLayout.setRefreshing(true);}}, 100);return mFragmentView;}@Overridepublic void onRefresh() {mSwipeToLoadLayout.postDelayed(new Runnable() {@Overridepublic void run() {mSwipeToLoadLayout.setRefreshing(false);}}, 3000);}
}

  最后,在MainActivity中动态加载碎片,如下:

ELeMaRefreshFragment eLeMaRefreshFragment = new ELeMaRefreshFragment();
mFragmentManager.beginTransaction().replace(R.id.content_frame, eLeMaRefreshFragment).commit();

饿了吗下拉刷新效果如下所示:
这里写图片描述
笔者还写了其它六个下拉刷新效果,思路大致相同,这里简单展示几个,大家可以去下载源码,然后自己运行看一下效果,便于理解代码;
京东下拉刷新效果如下所示:
这里写图片描述
美团外卖下拉刷新效果如下所示:
这里写图片描述
百度外卖下拉刷新效果如下所示:
这里写图片描述
天猫下拉刷新效果如下所示:
这里写图片描述
完整代码:
更多效果展示及笔者demo的源代码,可以去笔者的GitHub中查看,欢迎大家下载;
源代码地址:https://github.com/henryneu/SwipeRefreshDemo

这篇关于SwipeToLoadLayout布局中添加自定义控件仿美团、饿了吗等下拉效果的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

C#实现WinForm控件焦点的获取与失去

《C#实现WinForm控件焦点的获取与失去》在一个数据输入表单中,当用户从一个文本框切换到另一个文本框时,需要准确地判断焦点的转移,以便进行数据验证、提示信息显示等操作,本文将探讨Winform控件... 目录前言获取焦点改变TabIndex属性值调用Focus方法失去焦点总结最后前言在一个数据输入表单

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用

SpringBoot 自定义消息转换器使用详解

《SpringBoot自定义消息转换器使用详解》本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧... 目录一、前言二、SpringBoot 内容协商介绍2.1 什么是内容协商2.2 内容协商机制深入理解2.2.1 内容

使用Python实现生命之轮Wheel of life效果

《使用Python实现生命之轮Wheeloflife效果》生命之轮Wheeloflife这一概念最初由SuccessMotivation®Institute,Inc.的创始人PaulJ.Meyer... 最近看一个生命之轮的视频,让我们珍惜时间,因为一生是有限的。使用python创建生命倒计时图表,珍惜时间

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

防近视护眼台灯什么牌子好?五款防近视效果好的护眼台灯推荐

在家里,灯具是属于离不开的家具,每个大大小小的地方都需要的照亮,所以一盏好灯是必不可少的,每个发挥着作用。而护眼台灯就起了一个保护眼睛,预防近视的作用。可以保护我们在学习,阅读的时候提供一个合适的光线环境,保护我们的眼睛。防近视护眼台灯什么牌子好?那我们怎么选择一个优秀的护眼台灯也是很重要,才能起到最大的护眼效果。下面五款防近视效果好的护眼台灯推荐: 一:六个推荐防近视效果好的护眼台灯的

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。