欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义下拉刷新控件RefreshableView

程序员文章站 2024-03-31 16:57:46
这是在了解下拉刷新功能原理下的产物,下拉刷新可以说是国产app里面必有的功能,连google都为此出了swiperefreshlayout,一种md风格的下拉刷新。 不...

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

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

第一张图片展示的是textview:

Android自定义下拉刷新控件RefreshableView

第二章图片展示的是listview:

Android自定义下拉刷新控件RefreshableView

第三章图片展示的是scrollview:

Android自定义下拉刷新控件RefreshableView

基本上这就是我测试的成功的控件,兼容普通的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;

/**
 * created by cjh on 16-9-6.
 */
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;
 }

 /**
 * 判断是否可以下拉
 *
 * @return
 */
 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下拉刷新控件

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。