iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >Android自定义下拉刷新控件RefreshableView
  • 509
分享到

Android自定义下拉刷新控件RefreshableView

Android 2022-06-06 06:06:47 509人浏览 泡泡鱼
摘要

这是在了解下拉刷新功能原理下的产物,下拉刷新可以说是国产APP里面必有的功能,连Google都为此出了SwipeRefreshLayout,一种MD风格的下拉刷新。 不过,M

这是在了解下拉刷新功能原理下的产物,下拉刷新可以说是国产APP里面必有的功能,连Google都为此出了SwipeRefreshLayout,一种MD风格的下拉刷新。
不过,MD风格在国内似乎很是艰难,不单单是国内系统主流仍是4.4的原因,也有用户习惯的问题,扯的有点多了,在看了许多博客之后,我突然想写一个能仿照 SwipeRefreshLayout 的兼容所有控件的下拉刷新,不单单只是 ListView,希望它也可以包容普通的View和ScrollView,经过两天的奋斗,终于搞定了,因为我的目的只是想要下拉刷新,所以功能很少,不过,如果能把下拉刷新搞定了,其它的功能,就可以慢慢堆砌起来了。

新系统的原因,无法给出合适的gif,只能截图了:

第一张图片展示的是TextView:

第二章图片展示的是ListView:

第三章图片展示的是ScrollView:

基本上这就是我测试的成功的控件,兼容普通的View是最简单的,复杂一点的就是ListView和ScrollView。

思路:我的思路和大部分博客都是一样的,自定义一个ViewGroup,然后将包含的要拖拽的控件的Touch事件交给 RefreshableView 处理,动态改变 headView 的 marginTop 的值,以上。当然,这其中会有一些细节要注意,比如 onTouch 方法的返回值的处理,还有子 View 的 MarginTop 值的处理。

源码

先是headView的布局文件:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="Http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:gravity="center"
 android:orientation="horizontal">
 <ImageView
 android:id="@+id/imageView_down"
 android:layout_width="14dp"
 android:layout_height="24dp"
 android:padding="2dp"
 android:src="@drawable/svg_down" />
 <ProgressBar
 android:visibility="gone"
 android:id="@+id/progressBar"
 style="?android:attr/progressBarStyle"
 android:layout_width="20dp"
 android:layout_height="20dp"
 android:progressDrawable="@drawable/progressBar" />
 <TextView
 android:padding="15dp"
 android:id="@+id/textView"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@string/pull_to_refresh"
 android:textSize="16sp" />
</LinearLayout>

接下来就是至关重要的类 RefreshableView:


package com.pull2refresh;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.Targetapi;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import shike.xianrou.com.pull2refresh.R;

public class RefreshableView extends LinearLayout implements View.OnTouchListener {
 private static final String TAG = "RefreshableView";
 private static final int REFRESHING = 0;//正在刷新
 private static final int ORIGINAL = REFRESHING + 1;//初始状态
 private static final int RELEASE_TO_REFRESHING = ORIGINAL + 1;//释放即将刷新的状态
 private int current_status = ORIGINAL;//当前最新状态
 private LinearLayout headView;//刷新layout
 private TextView textView;//刷新layout中的文字提示
 private ImageView imageView;//刷新layout中的箭头
 private ProgressBar progressBar;//刷新layout中的进度条
 private View view;//手指控制的下拉的View
 private int hideHeight;//刷新layout要隐藏的高度
 private boolean isablePull;//是否可以下拉,例如当 current_status = REFRESHIING 时是不可以下拉拖拽的
 private float yDown;//手指按下的坐标
 private int touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();//界限值,防止手指误触,过于灵敏
 private boolean firstLayout = true;//第一次调用onLayout的时候置为false
 private int maxMarginTop;//刷新Layout能拉下的最大距离
 private MarginLayoutParams marginLayoutParams;//刷新layout的MarginLayoutParams
 private String pull_to_refresh = "下拉可以刷新";
 private String release_to_refresh = "释放立即刷新";
 private String refreshing = "正在刷新…";
 private int original_margin = 0;//针对下拉的View存在MarginTop这中特殊值的处理
 public interface PullToRefreshListener {
 void onRefresh();
 }
 private PullToRefreshListener pullToRefreshListener;
 public void addPullToRefreshListener(PullToRefreshListener pullToRefreshListener) {
 this.pullToRefreshListener = pullToRefreshListener;
 }
 public RefreshableView(Context context) {
 super(context);
 init();
 }
 public RefreshableView(Context context, AttributeSet attrs) {
 super(context, attrs);
 init();
 }
 public RefreshableView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 init();
 }
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public RefreshableView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
 super(context, attrs, defStyleAttr, defStyleRes);
 init();
 }
 private void init() {
 headView = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.refresh_layout, null, true);
 imageView = (ImageView) headView.findViewById(R.id.imageView_down);
 progressBar = (ProgressBar) headView.findViewById(R.id.progressBar);
 textView = (TextView) headView.findViewById(R.id.textView);
 progressBar.setVisibility(View.GONE);
 setOrientation(VERTICAL);
 addView(headView, 0);
 }
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 super.onLayout(changed, l, t, r, b);
 Log.d(TAG, "onlayout");
 if (changed && firstLayout) {
  //将View的Touch时间的处理交给RefreshableView去处理
  view = getChildAt(1);
  view.setOnTouchListener(this);
  //刷新layout的 marginTop 的最大值设为刷新头的高度
  maxMarginTop = headView.getHeight();
  //要将控件完全隐藏起来,那么隐藏的高度就设置为控件的高度
  hideHeight = -headView.getHeight();
  marginLayoutParams = (MarginLayoutParams) headView.getLayoutParams();
  marginLayoutParams.topMargin = hideHeight;
  headView.setLayoutParams(marginLayoutParams);
  //这里必须将firstLayout设置为false,否则在处理Touch是件的过程中,headView在怎么调用setLayoutParams都会被置为初始的隐藏状态
  firstLayout = false;
  //如果子View是一个ViewGroup 那么就需要计算出子View的MarginTop的值,因为如果MarginTop不为0,那么子View的Y轴坐标和父View的坐标是不一样的
  if (view instanceof ViewGroup) {
  int[] childLocations = new int[2];
  int[] viewLocations = new int[2];
  view.getLocationOnScreen(viewLocations);
  ((ViewGroup) view).getChildAt(0).getLocationOnScreen(childLocations);
  original_margin = childLocations[1] - viewLocations[1];
  Log.d(TAG, "onLayout viewLocations[1] " + viewLocations[1]);
  Log.d(TAG, "onLayout locations[1] " + childLocations[1]);
  Log.d(TAG, "onLayout original_margin " + original_margin);
  }
 }
 }
 @Override
 public boolean onTouch(View view, MotionEvent motionEvent) {
 if (pullToRefreshListener != null) {
  isAblePull();
  if (isablePull) {
  switch (motionEvent.getAction()) {
   case MotionEvent.ACTION_DOWN:
   yDown = motionEvent.getRawY();
   break;
   case MotionEvent.ACTION_MOVE:
   float yMove = motionEvent.getRawY();
   float distance = yMove - yDown;
   //如果手势是向上的,并且手势在Y轴的移动距离小于界限值,那么就不处理
   if (distance < 0 || Math.abs(distance) < touchSlop)
    return false;
   //MarginTop的距离是手势距离的1/2,形成费力延迟的效果
   marginLayoutParams.topMargin = (int) (distance / 2 + hideHeight);
   Log.d(TAG, "topMargin " + marginLayoutParams.topMargin);
   //如果大于最大的MarginTop的值的时候,就将值置为 maxMarginTop
   if (marginLayoutParams.topMargin >= maxMarginTop)
    marginLayoutParams.topMargin = maxMarginTop;
   if (marginLayoutParams.topMargin >= 0) {
    //当刷新头完全显示的时候,改变状态,置为 释放刷新的状态
    if (current_status != RELEASE_TO_REFRESHING) {
    rotate(0, 180);
    textView.setText(release_to_refresh);
    }
    current_status = RELEASE_TO_REFRESHING;
   } else {
    //否则就置为初始状态
    if (current_status != ORIGINAL) {
    rotate(180, 360);
    textView.setText(pull_to_refresh);
    }
    current_status = ORIGINAL;
   }
   headView.setLayoutParams(marginLayoutParams);
   break;
   case MotionEvent.ACTION_CANCEL:
   case MotionEvent.ACTION_UP:
   float yUp = motionEvent.getRawY();
   float dis = yUp - yDown;
   if (dis > 0 && Math.abs(dis) > touchSlop)
    switch (current_status) {
    //释放刷新
    case RELEASE_TO_REFRESHING:
     animateMarginTop(marginLayoutParams.topMargin, 0);
     imageView.clearAnimation();
     imageView.setVisibility(View.GONE);
     progressBar.setVisibility(View.VISIBLE);
     textView.setText(refreshing);
     pullToRefreshListener.onRefresh();
     break;
    //初始化
    case ORIGINAL:
     reset();
     //从当前的MarginTop的值,转变到隐藏所需的 MarginTop
     animateMarginTop(marginLayoutParams.topMargin, hideHeight);
     break;
    }
   else
    return false;
   break;
  }
  return true;
  }
 }
 return false;
 }
 
 public boolean isAblePull() {
 //统一判断,其实主要是对于view是普通View的判断
 if (current_status != REFRESHING)
  isablePull = true;
 else
  isablePull = false;
 if (view instanceof ViewGroup) {
  isablePull = false;
  View childView = ((ViewGroup) view).getChildAt(0);
  int[] viewLocations = new int[2];
  int[] childViewLocations = new int[2];
  view.getLocationOnScreen(viewLocations);
  childView.getLocationOnScreen(childViewLocations);
  //这一步中的 original_margin 至关重要,就是用来兼容 子View 有MarginTop属性的值,当childView 的Y轴坐标 和 计算出了 Margin 值后的父View坐标相等时,说明此时处于可下拉的状态
  if (viewLocations[1] + original_margin == childViewLocations[1])
  isablePull = true;
  else
  isablePull = false;
 }
 return isablePull;
 }
 private void rotate(int from, int to) {
 RotateAnimation rotateAnimation = new RotateAnimation(from, to, imageView.getWidth() / 2, imageView.getHeight() / 2);
 rotateAnimation.setDuration(100);
 rotateAnimation.setFillAfter(true);
 imageView.startAnimation(rotateAnimation);
 }
 private void animateMarginTop(int from, int to) {
 ObjectAnimator objectAnimator = ObjectAnimator.ofInt(headView, "cjh", from, to);
 objectAnimator.setDuration(300);
 objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator valueAnimator) {
  int margin = (int) valueAnimator.getAnimatedValue();
  marginLayoutParams.topMargin = margin;
  headView.setLayoutParams(marginLayoutParams);
  }
 });
 objectAnimator.start();
 }
 public void complete() {
 animateMarginTop(0, hideHeight);
 reset();
 }
 private void reset() {
 rotate(180, 360);
 textView.setText(pull_to_refresh);
 imageView.setVisibility(View.VISIBLE);
 progressBar.setVisibility(View.GONE);
 }
}

使用:


 <com.pull2refresh.RefreshableView
 android:id="@+id/refreshableView"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:gravity="center">
 <com.pull2refresh.MTextView
  android:id="@+id/mTextView"
  android:layout_width="match_parent"
  android:layout_height="100dp"
  android:background="@color/colorPrimary"
  android:gravity="center"
  android:text="Hello World!"
  android:textSize="22sp" />
 </com.pull2refresh.RefreshableView>

...
 refreshableView.addPullToRefreshListener(new RefreshableView.PullToRefreshListener() {
  @Override
  public void onRefresh() {
  setData();
  }
 });
...
 private void setData() {
 ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView, "text", 1f, 100f);
 objectAnimator.setDuration(3000);
 objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  @Override
  public void onAnimationUpdate(ValueAnimator valueAnimator) {
  textView.setText((Float) valueAnimator.getAnimatedValue());
  }
 });
 objectAnimator.addListener(new Animator.AnimatorListener() {
  @Override
  public void onAnimationStart(Animator animator) {
  }
  @Override
  public void onAnimationEnd(Animator animator) {
  refreshableView.complete();
  }
  @Override
  public void onAnimationCancel(Animator animator) {
  }
  @Override
  public void onAnimationRepeat(Animator animator) {
  }
 });
 objectAnimator.start();
 }

在我自定义的 RefreshableView 中,如果不设置下拉的监听,就没有下拉的效果,也就是不支持下拉

源码下载:Android下拉刷新控件

您可能感兴趣的文章:Android控件RefreshableView实现下拉刷新Android官方下拉刷新控件SwipeRefreshLayout使用详解Android UI设计系列之自定义ListView仿QQ空间阻尼下拉刷新和渐变菜单栏效果(8)Android自定义下拉刷新上拉加载Android RecyclerView实现下拉刷新和上拉加载Android下拉刷新上拉加载控件(适用于所有View)Android PullToRefreshLayout下拉刷新控件的终结者Android仿微信滑动弹出编辑、删除菜单效果、增加下拉刷新功能android开发教程之实现listview下拉刷新和上拉刷新效果Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能(附源码)


--结束END--

本文标题: Android自定义下拉刷新控件RefreshableView

本文链接: https://www.lsjlt.com/news/23311.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Android中怎么通过自定义控件实现下拉刷新效果
    本篇文章给大家分享的是有关Android中怎么通过自定义控件实现下拉刷新效果,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。@Override  protec...
    99+
    2023-05-30
    android
  • AndroidFlutter实现自定义下拉刷新组件
    目录前言改造点DIY下拉组件样式刷新时机调整效果展示前言 在Flutter开发中官方提供了多平台的下拉刷新组件供开发者使用,例如RefreshIndicator和CupertinoS...
    99+
    2024-04-02
  • Android自定义实现淘宝下拉刷新效果
    要实现淘宝下拉刷新效果,可以使用RecyclerView和SwipeRefreshLayout来实现,具体步骤如下:1. 在布局文件...
    99+
    2023-08-17
    Android
  • Android怎么自定义实现下拉刷新效果
    Android中自定义实现下拉刷新效果可以通过自定义View或者使用第三方库来实现。1. 自定义View:- 创建一个继承自View...
    99+
    2023-08-18
    Android
  • Android自定义ListView实现下拉刷新上拉加载更多
    目录1、创建刷新控件1.1、创建头部View1.2、下拉事件1.3、接口回调2、上拉加载更多2.1、底部样式2.2、布局添加Listview现在用的很少了,基本都是使用Recycle...
    99+
    2024-04-02
  • Android自定义实现顶部粘性下拉刷新效果
    本文实例为大家分享了Android实现顶部粘性下拉刷新效果的具体代码,供大家参考,具体内容如下学习:视频地址 activity_view_mv代码<?xml version="1.0" encoding="utf-8"?...
    99+
    2023-05-30
    android 顶部粘性 下拉刷新
  • Android SwipereFreshLayout下拉刷新
    Android SwipereFreshLayout下拉刷新我们都知道现在android5.0以后就提倡使用Material Design设计了。在Material Design设计就有一个非常好的设计SwipereFreshLayout,...
    99+
    2023-05-31
    android swiperefreshlayout 下拉刷新
  • React Native如何自定义下拉刷新上拉加载的列表
    小编给大家分享一下React Native如何自定义下拉刷新上拉加载的列表,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!在移动端...
    99+
    2024-04-02
  • Android中的RecyclerView下拉/上拉刷新数据
            在Android中的列表视图(我们这里以RecyclerView为例)中有很多数据的时候,往往要采取限制数据条目显示,然后通过刷新再添加新的数据显示,这样看的就会比较美观,那么这种列表视图是怎么实现刷新的呢,我们一起来看看吧...
    99+
    2023-09-07
    android
  • Android 实现的下拉刷新效果
    下面是自己实现的效果: 1、分析 可以将动画分解成: 睁眼毛驴绕着中心地球旋转,并且在到达地球中心时,切换为闭眼毛驴,最后发射出去 地球自我旋转,随着下拉而缓缓上升,达到半径距离后...
    99+
    2024-04-02
  • android 自定义控件 使用declare
    在Android中,可以使用`declare-styleable`来定义和使用自定义控件的属性。下面是一个简单的示例:1. 在res...
    99+
    2023-09-21
    Android
  • Android中怎么自定义Progress控件
    Android中怎么自定义Progress控件,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。主要就是需求就是椭圆进度,百分比跟随渐变背景,这样一想其实就是一个布局,然后控制...
    99+
    2023-05-31
    android progress
  • Android如何自定义评分控件
    今天小编给大家分享一下Android如何自定义评分控件的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。自定义参数为了方便扩展,...
    99+
    2023-06-30
  • Android入门篇自定义Button控件
    在Android中,可以通过继承Button类来创建自定义Button控件。下面是一个简单的例子,演示如何创建一个带有圆角背景和自定...
    99+
    2023-09-22
    Android
  • Android自定义实现日历控件
    本文实例为大家分享了Android自定义实现日历控件的具体代码,供大家参考,具体内容如下 1. Calendar类 2. 布局 创建calendar_layout.xml <...
    99+
    2024-04-02
  • Android自定义双向滑动控件
    本文实例为大家分享了Android自定义双向滑动控件的具体代码,供大家参考,具体内容如下 先看一下效果图 1.SeekBarPressure工具类 public class See...
    99+
    2024-04-02
  • Android自定义模拟时钟控件
    本文实例为大家分享了Android自定义模拟时钟控件的具体代码,供大家参考,具体内容如下 自定义view—透明模拟时钟显示 项目中要用到模拟时钟的显示,查了一些资料根据自...
    99+
    2024-04-02
  • android怎么自定义开关控件
    要自定义开关控件,可以使用以下步骤:1. 创建一个自定义的开关控件类,继承自Switch或CompoundButton类。2. 在自...
    99+
    2023-08-16
    android
  • android怎么自定义组合控件
    要自定义一个组合控件,你可以按照以下步骤进行:1. 创建一个新的类,继承自现有的Android控件类,例如LinearLayout或...
    99+
    2023-08-09
    android
  • android自定义View之复合控件
    复合控件可以很好地创建出具有重用功能的控件集合。 很多的APP都有一些共通的UI界面,为了统一应用程序的风格,下面我们就以一个Topbar为实例讲解复合控件。 实现效果如图: 第一...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作