Android实践之带加载效果的下拉刷新上拉加载更多

前言

之前写的一个LoadingBar,这次把LoadingBar加到下拉刷新的头部。从头写一个下拉刷新,附赠上拉加载更多。下面话不多说了,来一起看看详细的介绍吧。

效果图:

实现过程

首先是自定义属性,attrs.xml中定义头部的高度和上下的padding。

####attrs.xml####

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="PPRefreshView_header">
 <attr name="header_height" format="dimension"/>
 <attr name="header_padding" format="dimension"/>
 </declare-styleable>
</resources>

然后是头部的文件,里面放了一个TextView和一个图片

header_layout.xml####

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="50dp"
 android:orientation="vertical">
 <ImageView
 android:src="@mipmap/down"
 android:layout_centerVertical="true"
 android:id="@+id/icon"
 android:layout_width="20dp"
 android:layout_height="20dp"
 android:layout_toLeftOf="@+id/text"
 android:layout_marginRight="5dp"/>
 <TextView
 android:id="@+id/text"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_centerInParent="true"
 android:text="下拉刷新" />
</RelativeLayout>

然后是布局文件,让PPRefreshView作为父布局,下面可以放AbsListView的子类。

activity_main.xml####

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ppRefreshView="http://schemas.android.com/apk/res-auto"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="@color/white" tools:context="top.greendami.greendami.MainActivity">
 <top.greendami.greendami.PPRefreshView
 ppRefreshView:header_height="50dp"
 ppRefreshView:header_padding="10dp"
 android:id="@+id/swipeRefreshLayout"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#d4d4d4">
 <ListView
 android:background="@color/white"
 android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent"></ListView>
 </top.greendami.greendami.PPRefreshView>
</RelativeLayout>

最后是重点,下拉刷新的控件。

"精心"准备了一张图

####PPRefreshView.java####

package top.greendami.greendami;
package com.allrun.arsmartelevatorformanager.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.TextView;
import com.allrun.arsmartelevatorformanager.R;
import com.allrun.arsmartelevatorformanager.util.DPUnitUtil;
/**
 * Created by GreendaMI on 2017/3/21.
 */
public class PPRefreshView extends ViewGroup {
 Context context;
 RecyclerView mListView;
 PPView mPPView;
 View header;
 TextView title;
 ImageView mImage;//箭头
 int listTop = 0;
 float headerHeight = 10 + 30 + 10;//header的高度,上留白 + 文字(PPVIew)高度 + 下留白
 float headerpadding = 10;//上留白,下留白
 private int mYDown, mLastY;
 //最短滑动距离
 int a = 0;
 RotateAnimation mRotateAnimation;
 int state = 0; //0,正常;1,下拉;2,松开
 public void setPPRefreshViewListener(PPRefreshViewListener mPPRefreshViewListener) {
 this.mPPRefreshViewListener = mPPRefreshViewListener;
 }
 PPRefreshViewListener mPPRefreshViewListener;
 public PPRefreshView(Context context) {
 super(context);
 this.context = context;
 }
 public PPRefreshView(Context context, AttributeSet attrs) {
 super(context, attrs);
 this.context = context;
 //px转dp
 a = DPUnitUtil.px2dip(context,ViewConfiguration.get(context).getScaledDoubleTapSlop());
 TypedArray b = context.obtainStyledAttributes(attrs, R.styleable.PPRefreshView_header);
 headerHeight = b.getDimension(R.styleable.PPRefreshView_header_header_height, 100);
 headerpadding = b.getDimension(R.styleable.PPRefreshView_header_header_padding, 10);
 b.recycle();
 initAnima();
 }
 private void initAnima() {
 //箭头旋转
 mRotateAnimation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF,
 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
 mRotateAnimation.setFillAfter(true);
 mRotateAnimation.setDuration(200);
 }
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 if (mPPView != null) {
 mPPView.measure(widthMeasureSpec,(int) (headerHeight- 2 * headerpadding));
 }
 if (header != null) {
 header.measure(widthMeasureSpec, (int) (headerHeight- 2 * headerpadding));
 }
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 }
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 if (mListView == null && getChildCount() == 1) {
 mListView = (RecyclerView)getChildAt(0);
 mListView.setOnScrollListener(new RecyclerView.OnScrollListener() {
 @Override
 public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
  super.onScrolled(recyclerView, dx, dy);
  if(!recyclerView.canScrollVertically(1)){
  //添加外部回调
  if(mPPRefreshViewListener != null){  mPPRefreshViewListener.LoadMore();
  }
  }
 }
 });
 }
 if (mListView != null) {
 mListView.layout(l, listTop, getMeasuredWidth(), b);
 }
 if (mPPView != null) {
 //top:文字(PPVIew)高度 + 下留白
 mPPView.layout(l, (int)(listTop - headerHeight + headerpadding), r, listTop);
 }
 if (header != null) {
 //top:文字(PPView)高度 + 下留白
 header.layout(l, (int)(listTop - headerHeight + headerpadding), r, listTop);
 }
 }
 @Override
 protected void dispatchDraw(Canvas canvas) {
 super.dispatchDraw(canvas);
 //松开手指,list回到顶部
 if (state == 2) {
 listTop = listTop - 25;
 if (listTop < headerHeight) {
 listTop = (int)headerHeight;
 }
 requestLayout();
 }
 //刷新完毕,关闭header
 if (state == 0 && listTop > 0) {
 listTop = listTop - 25;
 if (listTop < 0) {
 listTop = 0;
 }
 requestLayout();
 }
 }
 @Override
 public boolean dispatchTouchEvent(MotionEvent event) {
 final int action = event.getAction();
 switch (action) {
 case MotionEvent.ACTION_DOWN:
 // 按下
 mYDown = (int) event.getRawY();
 break;
 case MotionEvent.ACTION_MOVE:
 // 移动
 mLastY = (int) event.getRawY();
 if (!mListView.canScrollVertically(-1) && mLastY > mYDown &&(mLastY - mYDown) > a) {
  state = 1;
  listTop = mLastY - mYDown;
  if (mPPView != null) {
  removeView(mPPView);
  mPPView = null;
  }
  if (header == null) {
  header = LayoutInflater.from(context).inflate(R.layout.header_layout, null, false);
  title = ((TextView) header.findViewById(R.id.text));
  mImage = ((ImageView) header.findViewById(R.id.icon));
  addView(header);
  }
  if (title != null && (mLastY - mYDown) > a * 2f) {
  title.setText("松开刷新");
  if (mImage.getAnimation() == null) {
  mImage.startAnimation(mRotateAnimation);
  }
  }
  if (title != null && (mLastY - mYDown) < a * 2f) {
  title.setText("下拉刷新");
  mImage.setImageResource(R.mipmap.down);
  }
  requestLayout();
  //已经判断是下拉刷新,拦截手势
  return false;
 }
 break;
 case MotionEvent.ACTION_UP:
 // 抬起
// if (canLoad()) {
//  loadData();
// }
 //松手的时候,把文字标题去掉
 if (header != null) {
  removeView(header);
  header = null;
 }
 //如果之前是下拉状态,就添加PPVIew
 if (mPPView == null && state == 1) {
  //添加外部回调
  if(mPPRefreshViewListener != null){
  mPPRefreshViewListener.onRefresh();
  }
  mPPView = new PPView(context);
  addView(mPPView);
  mYDown = 0;
  mLastY = 0;
  state = 2;
  requestLayout();
 }
 break;
 default:
 break;
 }
 return super.dispatchTouchEvent(event);
 }
 /**
 * 收起下拉刷新的header,刷新结束
 */
 public void RefreshOver() {
 if (mPPView != null) {
 removeView(mPPView);
 mPPView = null;
 }
 if (header != null) {
 removeView(header);
 header = null;
 title = null;
 mImage = null;
 }
 state = 0;
 }
 public void setRefreshing(boolean b) {
 if(!b){
 state = 0;
 postInvalidate();
 }else{
 state = 2;
 postInvalidate();
 }
 }
 public interface PPRefreshViewListener{
 void onRefresh();
 void LoadMore();
 }
}

主要思路是监听手势的滑动距离,如果ListView已经划到顶部,则ListView跟随手指位置,并添加Header。放开手指后,ListView慢慢回到顶部。

外部回调。监听下拉和上拉。

 mSwipeRefreshLayout.setPPRefreshViewListener(new PPRefreshView.PPRefreshViewListener() {
 @Override
 public void onRefresh() {
 Toast.makeText(MainActivity.this,"亲,刷新了",Toast.LENGTH_SHORT).show();
 data.add("测试数据100");
 mAdapter.notifyDataSetChanged();
 }
 @Override
 public void LoadMore() {
 Toast.makeText(MainActivity.this,"加载更多",Toast.LENGTH_SHORT).show();
 }
 });
refreshLayout.setRefreshing(false);;//刷新完毕

差点忘了粘连小球的View。

####PPView.java####
package top.greendami.greendami;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by GreendaMi on 2017/3/17.
 */
public class PPView extends View {
 String TAG = "PPView";
 //动画开关
 boolean isLoading = true;
 Context mContext;
 private int mWidth = 100;
 private int mheight = 100;
 public int mColor;
 public Paint mPaint = new Paint();
 float time = 0;
 //小球与中间打球的最远距离
 float distance = 100;
 public PPView(Context context) {
 super(context);
 mContext = context;
 mColor = context.getResources().getColor(R.color.colorPrimary);
 init();
 }
 public PPView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 mContext = context;
 mColor = context.getResources().getColor(R.color.colorPrimary);
 init();
 }
 private void init() {
 mPaint.setAntiAlias(true);
 mPaint.setColor(mColor);
 }
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
 //宽度至少是高度的4倍
 if (widthSpecSize < 4 * heightSpecSize) {
 widthSpecSize = 4 * heightSpecSize;
 }
 mWidth = widthSpecSize;
 mheight = heightSpecSize;
 distance = 1.2f * mheight;
 setMeasuredDimension(widthSpecSize, heightSpecSize);
 }
 @Override
 protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 if (isLoading) {
 //大圆半径
 float bigR = mheight * 0.32f + mheight * 0.03f * Math.abs((float) Math.sin(Math.toRadians(time)));
 float smallR = mheight * 0.17f + mheight * 0.03f * Math.abs((float) Math.cos(Math.toRadians(time)));
 float bigx = (getWidth()) / 2;
 //画中间大圆
 canvas.drawCircle(bigx, mheight / 2, bigR, mPaint);
 float smalx = getSmallCenterX();
 //画小圆
 canvas.drawCircle(smalx, mheight / 2, smallR, mPaint);
 //画链接
 //小球在右侧
 if (smalx > bigx) {
 Path path = new Path();
 //上面的贝塞尔曲线的第一个点,在大圆身上
 float x1 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
 float y1 = mheight / 2 - bigR * (float) Math.sin(Math.toRadians(time));
 if (y1 > mheight / 2 - smallR) {
  y1 = mheight / 2 - smallR;
  x1 = bigx + (float) (Math.sqrt(bigR * bigR - smallR * smallR));
 }
 //上面的贝塞尔曲线的第三个点,在小圆身上
 float x2 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
 float y2 = mheight / 2 - smallR * (float) Math.sin(Math.toRadians(time));
 if (y2 > mheight / 2 - smallR * 0.8) {
  y2 = mheight / 2 - smallR * 0.8f;
  x2 = smalx - smallR * (float) (Math.sqrt(1-0.64f));
 }
 //下面的贝塞尔曲线的第三个点,在小圆身上
 float x3 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
 float y3 = mheight / 2 + smallR * (float) Math.sin(Math.toRadians(time));
 if (y3 < mheight / 2 + smallR * 0.8) {
  y3 = mheight / 2 + smallR * 0.8f;
  x3 = smalx - smallR * (float) (Math.sqrt(1-0.64f));
 }
 //下面的贝塞尔曲线的第一个点,在大圆身上
 float x4 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
 float y4 = mheight / 2 + bigR * (float) Math.sin(Math.toRadians(time));
 if (y4 < mheight / 2 + smallR) {
  y4 = mheight / 2 + smallR;
  x4 = bigx + (float) (Math.sqrt(bigR * bigR - smallR * smallR));
 }
 path.moveTo(x1, y1);
 path.quadTo((bigx + smalx) / 2, mheight / 2, x2, y2);
 // 绘制贝赛尔曲线(Path)
 path.lineTo(x3, y3);
 path.quadTo((bigx + smalx) / 2, mheight / 2, x4, y4);
 canvas.drawPath(path, mPaint);
 }
 //小球在左侧
 if (smalx < bigx) {
 Path path = new Path();
 float x1 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
 float y1 = mheight / 2 - bigR * (float) Math.sin(Math.toRadians(time));
 if (y1 > mheight / 2 - smallR) {
  y1 = mheight / 2 - smallR;
  x1 = bigx - (float) (Math.sqrt(bigR * bigR - smallR * smallR));
 }
 float x2 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
 float y2 = mheight / 2 - smallR * (float) Math.sin(Math.toRadians(time));
 if (y2 > mheight / 2 - smallR * 0.8) {
  y2 = mheight / 2 - smallR * 0.8f;
  x2 = smalx + smallR * (float) (Math.sqrt(1-0.64f));
 }
 float x3 = smalx - smallR * (float) Math.cos(Math.toRadians(time));
 float y3 = mheight / 2 + smallR * (float) Math.sin(Math.toRadians(time));
 if (y3 < mheight / 2 + smallR * 0.8) {
  y3 = mheight / 2 + smallR * 0.8f;
  x3 = smalx + smallR * (float) (Math.sqrt(1-0.64f));
 }
 float x4 = bigx + bigR * (float) Math.cos(Math.toRadians(time));
 float y4 = mheight / 2 + bigR * (float) Math.sin(Math.toRadians(time));
 if (y4 < mheight / 2 + smallR) {
  y4 = mheight / 2 + smallR;
  x4 = bigx - (float) (Math.sqrt(bigR * bigR - smallR * smallR));
 }
 path.moveTo(x1, y1);
 path.quadTo((bigx + smalx) / 2, mheight / 2, x2, y2);
 // 绘制贝赛尔曲线(Path)

 path.lineTo(x3, y3);

 path.quadTo((bigx + smalx) / 2, mheight / 2, x4, y4);
 canvas.drawPath(path, mPaint);
 }

 postInvalidate();
 }
 }
 //计算小球的X坐标
 private float getSmallCenterX() {
 //此处控制速度
 time = time + 4f;
 return mWidth / 2 + distance * (float) Math.cos(Math.toRadians(time));
 }
}

两张素材

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • android使用PullToRefresh框架实现ListView下拉刷新上拉加载更多

    本文实例为大家分享了Android实现ListView下拉刷新上拉加载更多的具体代码,供大家参考,具体内容如下 其实谷歌官方目前已经推出ListView下拉刷新框架SwipeRefreshLayout,想了解的朋友可以点击 android使用SwipeRefreshLayout实现ListView下拉刷新上拉加载了解一下: 大家不难发现当你使用SwipeRefreshLayout下拉的时候布局文件不会跟着手势往下滑,而且想要更改这个缺陷好像非常不容易. 虽然SwipeRefreshLayout非

  • Android 仿硅谷新闻下拉刷新/上拉加载更多

    1.添加加载更多布局 1_初始化和隐藏代码 在RefreshListView构造方法中调用 private void initFooterView(Context context) { View footerView = View.inflate(context, R.layout.refresh_listview_footer, null); //隐藏代码 footerView.measure(0, 0); int footerViewHeight = footerView.getMeasur

  • Android实现上拉加载更多以及下拉刷新功能(ListView)

    首先为大家介绍Andorid5.0原生下拉刷新简单实现. 先上效果图: 相对于上一个19.1.0版本中的横条效果好看了很多.使用起来也很简单. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" and

  • Android下拉刷新上拉加载更多左滑动删除

    一.前言 老规矩,别的不说,这demo是找了很相关知识集合而成的,可以说对我这种小白来说是绞尽脑汁!程序员讲的是无图无真相! 现在大家一睹为快! 二.比较关键的还是scroller这个类的 package com.icq.slideview.view; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue;

  • Android ListView实现上拉加载更多和下拉刷新功能

    本文实例为大家介绍了Android ListView下拉刷新功能的实现方法和功能,供大家参考,具体内容如下 1.ListView优化方式 界面缓存:ViewHolder+convertView 分页加载:上拉刷新 图片缓存 快速滑动ListView禁止刷新 2.效果 3.上拉加载更多原理及实现 当我们手指滑动到listview最后位置的时候,我们触发加载数据的方法.这触发之前我们需要做一些工作,包括: 如何判断滑动到最后? 如何避免重复加载数据? 加载之后如何刷新界面? 1).界面实现AbsLi

  • Android RecyclerView下拉刷新和上拉加载更多

    今天终于有点时间,来写了一下: 为RecyclerView实现下拉刷新和上拉加载更多.今天会在前面的两篇文章的基础上: RecyclerView系列之(1):为RecyclerView添加Header和Footer RecyclerView系列之(2):为RecyclerView添加分隔线 继续讲述RecyclerView中一些常用组件的实现下拉刷新和上拉加载更多的功能. 在现在的Android手机应用中,几乎每一个APP都有下拉刷新和上拉加载更多的功能,它们的重要性不言而喻. 先不多说,先看效

  • Android RecyclerView 上拉加载更多及下拉刷新功能的实现方法

    RecyclerView 已经出来很久了,但是在项目中之前都使用的是ListView,最近新的项目上了都大量的使用了RecycleView.尤其是瀑布流的下拉刷新,网上吧啦吧啦没有合适的自己总结了一哈. 先贴图上来看看: 使用RecyclerView实现上拉加载更多和下拉刷新的功能我自己有两种方式: 1.使用系统自带的Android.support.v4.widget.SwipeRefreshLayout这个控价来实现. 2.自定义的里面带有RecyleView的控件. 使用RecycleVie

  • Android RecyclerView实现下拉刷新和上拉加载更多

    使用官方的刷新控件SwipeRefreshLayout来实现下拉刷新,当RecyclerView滑到底部实现下拉加载(进度条效果用RecyclerView加载一个布局实现) 需要完成控件的下拉监听和上拉监听,其中,下拉监听通过SwipRefreshLayout的setOnRefreshListener()方法监听,而上拉刷新,需要通过监听列表的滚动,当列表滚动到底部时触发事件,具体代码如下 主布局 <?xml version="1.0" encoding="utf-8&

  • Android XListView下拉刷新和上拉加载更多

    市面上有好多的类比ListView刷新数据的开源框架,如:v4包自带的SwipeRefreshLayout ,以及集ListView.GridView甚至WebView于一身的Pulltorefresh等等.前述的两个开源框架目前使用也算频繁.有兴趣的读者可以自行搜索,当然有时间一定回来对所有的使用方式做一个汇总和比较.今天介绍的这款框架,专门针对ListView做下拉刷新与上拉加载的,如果单单是ListView就显得更加简单方便易于理解. 1.首先引入xListView_lib库到自己的Dem

  • Android中Listview下拉刷新和上拉加载更多的多种实现方案

    listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明. 方案一:添加头布局和脚布局 android系统为listview提供了addfootview和addheadview两个API.这样可以直接自定义一个View,以添加视图的形式实现下来刷新和上拉加载. 实现步骤    1.创建一个类继承ListView:class PullToRefreshListView extends ListView: 2.在构造方法中添加HeadView:addHeaderVi

随机推荐