Android控件PullRefreshViewGroup实现下拉刷新和上拉加载

本文实例为大家分享了Android实现下拉刷新和上拉加载更多的具体代码,供大家参考,具体内容如下

先分享下源码:Android实现下拉刷新和上拉加载更多

实现思路:由PullRefreshViewGroup控件来接管标准控件(比如RecyclerView、ListView等)的滑动,调用标准控件的内部方法进行短距离滑动,不再由标准控件自己来处理事件,而完全由PullRefreshViewGroup控件来处理触摸事件。标准控件内部的滑动距离等属性,通过反射获得computeVerticalScrollExtent、computeVerticalScrollRange、computeVerticalScrollOffset这三个方法来获得。

PullRefreshViewGroup控件的布局如下

部分代码实现

触摸滑动事件处理

@Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  boolean bret = false;
  switch (ev.getAction()) {
   case MotionEvent.ACTION_DOWN: {
    mPreY = ev.getY();
    Log.d(TAG, "mPreY:" + String.valueOf(mPreY));
   }
   break;
   case MotionEvent.ACTION_MOVE: {
    float curY = ev.getY();
    float distance = curY - mPreY;
    if (Math.abs(distance) >= mTouchSlop) {
     mSliding = bret = true; 

     //修正第一次滑动的卡顿
     if (distance > 0) {
      mPreY += mTouchSlop;
     } else {
      mPreY -= mTouchSlop;
     } 

     if (!mScroller.isFinished()) {
      mScroller.abortAnimation();
     }
    } else {
     mSliding = bret = false;
    }
   }
   break;
   case MotionEvent.ACTION_UP:
   case MotionEvent.ACTION_CANCEL: {
    bret = mSliding;
   }
   break;
  }
  return bret ? true : super.onInterceptTouchEvent(ev);
 } 

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  boolean bret = false; 

  vTracker.addMovement(event); 

  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN: {
    mPreY = event.getY();
    bret = true;
   }
   break;
   case MotionEvent.ACTION_MOVE: {
    float curY = event.getY();
    float distance = curY - mPreY;
    Log.d(TAG, "mPreY:" + String.valueOf(mPreY) + " distance:" + String.valueOf(distance));
    if (distance != 0) {
     bret = true; 

     if (mSrcHeightHead == -1 && mHasPullRefresh) {
      View child0View = mHeadView;
      mSrcHeightHead = child0View.getHeight();
     } 

     scrollBy(distance);
    } 

    mPreY = curY;
   }
   break;
   case MotionEvent.ACTION_UP:
   case MotionEvent.ACTION_CANCEL: {
    mPreCurY = 0; 

    View child0View = mHeadView;
    int child0Height = null != child0View ? child0View.getHeight() : 0;
    int child0Height2 = null != child0View ? child0View.getLayoutParams().height : 0; //视图的最终高度是有这个来决定的,请看onMeasure 函数的实现
    // int child0Height3 = child0View.getMeasuredHeight();
    if (child0Height2 != child0Height) {
     child0Height = child0Height2;
    }
    int child0Top = null != child0View ? child0View.getTop() : 0;
    int dy = child0Height - mSrcHeightHead + (mSrcHeightHead - Math.abs(child0Top));
    Log.d(TAG, "onTouchEvent()ACTION_UP child0Height:" + String.valueOf(child0Height) + " mSrcHeightHead:" + String.valueOf(mSrcHeightHead) + " child0Top:" + String.valueOf(child0Top));
    if (dy > 0) {//恢复拉伸视图的位置
     if (!mLoadingMore && dy > mCanRefreshHeight && child0Top + child0Height2 > mCanRefreshHeight && mRefreshLoad != null) {
      dy -= mCanRefreshHeight; 

      if (!mPullRefreshLoading) {
       mPullRefreshLoading = true;
       mTvRefreshInfo.setText("正在加载...");
       mRefreshLoad.pullRefreshStartLoad();
      }
     }
     mScroller.startScroll(0, 0, 0, -dy);
     invalidate();
    } else {
     vTracker.computeCurrentVelocity(1000); 

     float yvel = vTracker.getYVelocity();
     if (yvel != 0) {//为了满足内部视图的快速滚动( 中间内容视图 )
      mScroller.fling(0, 0, 0, (int) yvel, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
      invalidate();
     }
    } 

    vTracker.clear();
    bret = true;
   }
   break;
  }
  return bret ? true : super.onTouchEvent(event);
 }

小距离滑动代码

private void scrollBy(float distance) {
  View child0View = mHeadView;
  View child1View = getChildAt(null == mHeadView ? 0 : 1);
  float distanceRemain = 0; 

  int child0Top = null != child0View ? child0View.getTop() : 0;
  // int child0Height = child0View.getHeight(); 

  if (distance < 0) {//向上
   int child1Top = child1View.getTop();
   // int child1Height = child1View.getHeight(); 

   //child0View 缩小
   if (-1 != mSrcHeightHead && null != child0View && child0View.getHeight() > mSrcHeightHead) {
    float off = distance;
    if (child0View.getHeight() + distance < mSrcHeightHead) {
     off = -(child0View.getHeight() - mSrcHeightHead);
     distance -= off;
    } else {
     distance = 0;
    } 

    child0View.getLayoutParams().height += (int) off;
    child1Top += (int) off; //child0view 缩小的同时, child1view 的高度也会随之上升 这里很重要
    requestLayout();
    child1View.offsetTopAndBottom((int) off);
    if (null != mTailView) {
     mTailView.offsetTopAndBottom((int) off);
    }
   } 

   if (distance != 0) {
    if (child0Top + mSrcHeightHead + distance <= 0) {
     distanceRemain = -(distance + (child0Top + mSrcHeightHead));//正数
     distance = -(child0Top + mSrcHeightHead);//负数
    } 

    //可以显示加载更多吗?
    boolean bDirectDown = false;
    boolean bCanScroll = child1View.canScrollVertically(1) || child1View.canScrollVertically(-1);
    if (!bCanScroll) {
     int child1ChildCount = 0;
     if (child1View instanceof ViewGroup) {
      child1ChildCount = ((ViewGroup) child1View).getChildCount();
     }
     if (child1ChildCount > 0) {
      ViewGroup viewGroupChild1 = (ViewGroup) child1View; 

      View child1LastItem = viewGroupChild1.getChildAt(child1ChildCount - 1);
      int child1ViewBottom = viewGroupChild1.getBottom();
      int child1LastItemBottom = child1LastItem.getBottom() + child1Top; //相对于 ImageScaleViewGroup 的位置
      //增加 child1ViewBottom > getHeight() 来控制 ScrollView
      if (child1LastItemBottom == getHeight()) {
       bDirectDown = true;
      }
     }
    }
    //正在下拉刷新的时候,不能显示加载更多
    if ((bCanScroll || bDirectDown) && null != mTailView && !mPullRefreshLoading) { 

     int nVerticalScrollExtent = 0, nVerticalScrollRange = 0, nVerticalScrollOffset = 0;
     Class c = null; 

     try {
      c = Class.forName(child1View.getClass().getName());
     } catch (Exception ex) { 

     }
     try {
      if (null == mComputeVerticalScrollExtent) {
       Method computeVerticalScrollExtent = findcomputeVerticalMethod(c, "computeVerticalScrollExtent");
       computeVerticalScrollExtent.setAccessible(true);
       mComputeVerticalScrollExtent = computeVerticalScrollExtent;
      }
      nVerticalScrollExtent = (int) mComputeVerticalScrollExtent.invoke(child1View);
     } catch (Exception ex) { 

     }
     try {
      if (null == mComputeVerticalScrollRange) {
       Method computeVerticalScrollRange = findcomputeVerticalMethod(c, "computeVerticalScrollRange");
       computeVerticalScrollRange.setAccessible(true);
       mComputeVerticalScrollRange = computeVerticalScrollRange;
      } 

      nVerticalScrollRange = (int) mComputeVerticalScrollRange.invoke(child1View);
     } catch (Exception ex) { 

     }
     try {
      if (null == mComputeVerticalScrollOffset) {
       Method computeVerticalScrollOffset = findcomputeVerticalMethod(c, "computeVerticalScrollOffset");
       computeVerticalScrollOffset.setAccessible(true);
       mComputeVerticalScrollOffset = computeVerticalScrollOffset;
      }
      nVerticalScrollOffset = (int) mComputeVerticalScrollOffset.invoke(child1View);
     } catch (Exception ex) { 

     } 

     int range = nVerticalScrollRange - nVerticalScrollExtent;
     if (nVerticalScrollOffset + distanceRemain > range) {
      float nOff = distanceRemain - (range - nVerticalScrollOffset); 

      distanceRemain = range - nVerticalScrollOffset;
      distance -= nOff;
     } 

     int child3Bottom = mTailView.getBottom(); 

     if (child3Bottom + distance < getHeight()) {
      distance = getHeight() - child3Bottom;
     }
    } 

    if (!bCanScroll) {
     distanceRemain = 0;
    }
   } 

  } else {//向下
   int nScrollOffset = 0;
   try {
    Class c = Class.forName(child1View.getClass().getName());
    Method computeVerticalScrollOffset = findcomputeVerticalMethod(c, "computeVerticalScrollOffset");//c.getDeclaredMethod("computeVerticalScrollOffset");
    computeVerticalScrollOffset.setAccessible(true);
    nScrollOffset = (int) computeVerticalScrollOffset.invoke(child1View);
   } catch (Exception ex) { 

   } 

   int child2Top = null != mTailView ? mTailView.getTop() : getHeight();//注意默认值
   if (child2Top < getHeight()) {
    if (child2Top + distance > getHeight()) {
     distanceRemain = distance - (getHeight() - child2Top);
     distance = getHeight() - child2Top;
    }
   } else if (nScrollOffset > 0) {//内部有滚动,那么就要计算内部滚动距离,其他分配给整体滚动
    if (nScrollOffset - distance <= 0) {
     distanceRemain = -nScrollOffset;
     // distance = distance - nScrollOffset;
     distance = 0; //内部能滚动,不让外部去滚动
     if (!mScroller.isFinished()) {
      mScroller.abortAnimation(); //内部滚动完后,立即停止
     }
    } else {
     distanceRemain = -distance;//负数
     distance = 0;
    }
   } else {
    if (child0Top + distance > 0) {//child0放大,child1移动
     int off = (int) (child0Top + distance);
     distance = -child0Top; 

     if (null != child0View) {
      child0View.getLayoutParams().height += off;
      requestLayout();
     } else {
      off = 0;
     } 

     child1View.offsetTopAndBottom(off);
     if (null != mTailView) {
      mTailView.offsetTopAndBottom(off);
     }
    }
   }
  } 

  if (0 != (int) distance) {
   if (null != child0View) {
    child0View.offsetTopAndBottom((int) distance);
   }
   child1View.offsetTopAndBottom((int) distance);
   if (null != mTailView) {
    mTailView.offsetTopAndBottom((int) distance);
   } 

   requestLayout();//奇酷360这里必须调用, 否则显示有点问题
  } 

  scrollByForMidView(distanceRemain);//外部无法滚动的时候,内部滚动 

  if (!mPullRefreshLoading && !mLoadingMore) {
   int tailviewTop = null != mTailView ? mTailView.getTop() : getHeight();//注意默认值 

   if (tailviewTop < getHeight() && mHasLoadMore) {//加载更多
    mLoadingMore = true;
    if (mRefreshLoad != null) {
     mRefreshLoad.pullUpStartLoadMore();
    }
   } else {
    if (mHasPullRefresh) {
     if (distance < 0) {
      int child0Bottom = child0View.getBottom();
      if (child0Bottom < mCanRefreshHeight) {
       mTvRefreshInfo.setText("下拉刷新");
      }
     } else {
      int child0Bottom = child0View.getBottom();
      if (child0Bottom > mCanRefreshHeight) {
       mTvRefreshInfo.setText("松开刷新");
      }
     }
    }
   }
  }
 }

处理标准控件小距离滚动代码,这里ListView有点特殊。

private void scrollByForMidView(float distanceRemain) {
  if (0 != (int) distanceRemain) {
   View child1View = getChildAt(null == mHeadView ? 0 : 1);
   if (child1View instanceof ListView) {
    ((ListView) child1View).smoothScrollBy((int) distanceRemain, 0);
   } /*else if (child1View instanceof ScrollView){
    ((ScrollView) child1View).smoothScrollBy(0,(int) distanceRemain);
   }*/ else {
    child1View.scrollBy(0, (int) distanceRemain);
   }
  }
 } 

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

时间: 2017-03-04

android LinearLayout和RelativeLayout组合实现精确布局方法介绍

先明确几个概念的区别: padding margin都是边距的含义,关键问题得明白是什么相对什么的边距. padding是控件的内容相对控件的边缘的边距. margin是控件边缘相对父空间的边距.  android:gravity 属性是对该view 内容的限定.比如一个button 上面的text. 你可以设置该text 在view的靠左,靠右等位置.该属性就干了这个. android:layout_gravity是用来设置该view中的子view相对于父view的位置.比如一个button

基于Android实现3D翻页效果

最近做了一个简单的3D效果翻页特效,先说说我的思路吧,首先我这个翻页效果并不是两个Activity之间的跳转,而是在同一个activity类切换不同的view而已.我现在的做法是单击一个button然后Gone当前的布局,然后把需要呈现的布局visible,在隐藏当前布局的时候启动动画,然后给动画添加监听,在动画结束时开始另外一个view的入场动画就行了. 下面来看下我的主页面的布局文件: <FrameLayout xmlns:android="http://schemas.android

android中图片翻页效果简单的实现方法

复制代码 代码如下: public class PageWidget extends View {    private Bitmap foreImage;    private Bitmap bgImage;    private PointF touchPt;    private int screenWidth;    private int screenHeight;    private GradientDrawable shadowDrawableRL;    private Gra

Android应用借助LinearLayout实现垂直水平居中布局

首先说的是LinearLayout布局下的居中一般是这样的: (注意:android:layout_width="fill_parent" android:layout_height="fill_parent" 属性中,若水平居中,至少在宽度上占全屏:若垂直居中,则在高度上占全屏) <LinearLayout android:layout_width="fill_parent" android:layout_height="fil

Android通过手势实现答题器翻页效果

本文实例为大家分享了Android答题器翻页功能,主要使用ViewFilpper和GestureDetector来实现,供大家参考,具体内容如下 1.效果图 2.实现思路 把Activity的TouchEvent事件交个GestureDetector来处理,然后使用ViewFilpper使用动画控制多个组件的之间的切换效果.手势的一个Api就不详细说了,大家如果不了解可以查一下. 3.实现的步骤 1).构建手势检测器 2).准备数据 3).为ViewFilpper添加子控件. 4).初始化Ani

Android App中的多个LinearLayout嵌套布局实例解析

在做android  UI布局时,用了LinearLayout嵌套,发现效果并不如我预料一般 查了下资料,说是要设置layout_weight属性 资料说得不是很清楚,也没仔细看,就去弄,结果越弄越混乱. 于是静下心来,自己写xml测试,发现如下. 如果LinearLayout是最外面的一层,它是不会弹出layout_weight属性的, 换句话说最外层不能用layout_weight xml布局如下 <LinearLayout xmlns:android="http://schemas.

解析Android中实现滑动翻页之ViewFlipper的使用详解

1)View切换的控件-ViewFlipper介绍 ViewFilpper类继承于ViewAnimator类.而ViewAnimator类继承于FrameLayout. 查看ViewAnimator类的源码可以看出此类的作用主要是为其中的View切换提供动画效果.该类有如下几个和动画相关的方法. setInAnimation:设置View进入屏幕时候使用的动画.该方法有两个重载方法,即可以直接传入Animation对象,也可以传入定义的Animation文件的resourceID. setOut

Android编程使用LinearLayout和PullRefreshView实现上下翻页功能的方法

本文实例讲述了Android编程使用LinearLayout和PullRefreshView实现上下翻页功能的方法.分享给大家供大家参考,具体如下: 前看过网易云阅读客户端,里面的文章可以实现上下拉动实现上下翻页的效果,感觉体验效果很不错. 公司新版本项目的开发中也要求实现类似的效果,不过还好项目需求里面可以提前知道需要实现上下拉动翻页的总的页数.如果像网易那种不提前知道总的页数感觉控制好LinearLayout里面的childView应该也可以达到效果. 好记性不如烂笔头,先写下我提前知道总页

Android利用悬浮按钮实现翻页效果

今天给大家分享下自己用悬浮按钮点击实现翻页效果的例子. 首先,一个按钮要实现悬浮,就要用到系统顶级窗口相关的WindowManager,WindowManager.LayoutParams.那么在AndroidManifest.xml中添加权限: <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 然后,我们要对WindowManager,WindowManager.Layout

Android中LinearLayout布局的常用属性总结

基本属性要求 <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> </LinearLayout> android:orientation 决定是水平排列或是垂直排列 vertical 垂直排列 horizontal 水平排列 垂直排列 Bu

android ViewPager实现滑动翻页效果实例代码

实现ViewPager的滑动翻页效果可以使用ViewPager的setPageTransformer方法,如下: import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.View; public class ReadViewPager extends ViewPager { public ReadV

android LinearLayout 布局实例代码

复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?>  <!--      <LinearLayout>         线性版面配置,在这个标签中,所有元件都是按由上到下的排队排成的   --> <LinearLayout      xmlns:android="http://schemas.android.com/apk/res/android"    

Android分页中显示出下面翻页的导航栏的布局实例代码

当页面条目过多的时候需要分页,要在布局中显示出分页的相关布局,使用android:layout_weight="11" activity_call_safe.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:lay

Android 自定义状态栏实例代码

一.目标:Android5.0以上 二.步骤 1.在res-values-colors.xml下新建一个RGB颜色 <?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#3

Android 滑动拦截实例代码解析

废话不多说了,直接给大家贴代码了,具体代码如下所示: package demo.hq.com.fby; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; /** * Created by huqing on 2016/12/7.

Android Dialog对话框实例代码讲解

Dialog的基本方法 //创建Dialog AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); //设置标题图标 builder.setIcon(R.drawable.ic_launcher); //设置标题 builder.setTitle("这是一个对话框"); //设置信息 builder.setMessage("是否要跳转?"); //确定按钮 setPosit

第七篇Bootstrap表单布局实例代码详解(三种表单布局)

Bootstrap提供了三种表单布局:垂直表单,内联表单和水平表单.下面逐一给大家介绍,有兴趣的朋友一起学习吧. 创建垂直或基本表单: •·向父 <form> 元素添加 role="form". •·把标签和控件放在一个带有 class .form-group 的 <div> 中.这是获取最佳间距所必需的. •·向所有的文本元素 <input>.<textarea> 和 <select> 添加 class .form-cont

Android 日期选择器实例代码

废话不多说了,直接给大家贴代码了,具体代码如下所示: //出生年月设置 private void birthSetting() { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR, 1); new DatePickerDialog(mContext, new DatePickerDialog.OnDateSetListener() { @Override public void onDat

Android 获取时间实例代码

 Android 获取时间实例代码 注意: h:12小时制小时数 H:24小时制小时数 实例代码: import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; /** * Created by Administrator on 2017/5/8. */ public

Android 指纹功能实例代码

最近在做项目的时候遇到了添加打开app图像解锁的功能,自己嘴欠说现在都用指纹功能,自己给自己挖了一个坑,真是没谁了 从网上看了一些资料,但是给我demo考虑的不是很多,设备支不支持都没考虑,如果支持的话是否添加过指纹也不知道,其实方法都很简单. 废话不多说,贴上工具类和使用方法 package com.tsm.test; import android.annotation.TargetApi; import android.app.Activity; import android.app.Key

vue.js通过路由实现经典的三栏布局实例代码

经典的三栏布局效果图如下: 三栏布局 •将布局的各个区块定义成组件 <template id="header"> <div class="header bg-primary text-center"> <h3>头部区域</h3> </div> </template> <template id="left"> <div class="left bg-