首页 > 代码库 > listview下拉刷新
listview下拉刷新
x写控件挺麻烦的,因为有很多细节要处理好,列表控件使用太频繁了,网上也各种自定义的方法,一般的listview自定义肯定会联想到加个头部,然后监听事件加动画,其实方式很多种,今天记录的方式是另外一种方式,个人觉得复用性更强,写好了可以通用,思路就是在不动原列表控件的情况下给它上面套个壳,然后让壳来操作刷新显示,这样的话是不是以后要用的时候加个壳就行了,而且我可以基本上不管里面的控件是什么。
MainActivity很简单,啥都不用做,按着listview加载数据的方式加载就好了
public class MainActivity extends AppCompatActivity { private ListView listView; private Adapter adapter = null; private List<String> list = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); list(); } private void list(){ listView = (ListView) findViewById(R.id.listview); for(int i = 0;i < 20; i++){ list.add(""+i); } adapter = new Adapter(MainActivity.this,R.layout.item_line,list); listView.setAdapter(adapter); final TwinklingRefreshLayout refreshLayout = (TwinklingRefreshLayout) findViewById(R.id.refreshLayout); } }
重点都在这个壳里了,注释非常详细,因为壳是父容器,所以不用传递视图进去,这样也会产生依赖关系,当然,这只是个半成品,因为里面很多东西没想好怎么更好的去做,才能达到我想要的结果,不过基本的雏形都有了,界面开始显示listview,监听时间滑动,然后判断滑动的程度,距离,是否在刷新中等等,接着通过更新listview的MarginTop来展示头部的刷新,而头部都是在加载事件里面动态添加进去的,以后也可以更换配置头部样式,这样xml里也不用特地的为这个控件去写特定的布局,不然这种做法就失去了意义了,和直接添加头部也没什么大区别了,显示出了头部根据你滑动的状态来控制动画,然后已经运行了动画就不能让它在更新top了,不然重复刷新,接着抬起操作,此处我直接设置的两秒完成,实际是访问回调接口停止的
package cn.com.listwebview.demo; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import cn.com.listwebview.demo.utils.DensityUtil; /** * Created by LiuZhen on 2017/3/24. */ public class TwinklingRefreshLayout extends LinearLayout{ private String TAG = "TwinklingRefreshLayout"; private int downY;// 按下时y轴的偏移量 private final static float RATIO = 3f; //头部的高度 protected int mHeadHeight; //头部layout protected FrameLayout mHeadLayout;//头部父容器 private HeaderView mHeadView;//头部 protected FrameLayout mFootLayout;//头部父容器 private ImageView ivArrow_left,ivArrow_right; //头布局的剪头 // private ProgressBar mProgressBar; // 头布局的进度条 private Animation upAnimation;// 向上旋转的动画 private Animation downAnimation;// 向下旋转的动画 private final int DOWN_PULL_REFRESH = 0;// 下拉刷新状态 private final int RELEASE_REFRESH = 1;// 松开刷新 private final int REFRESHING = 2;// 正在刷新中 private final int END = 3;// 正在刷新中 private int currentState = DOWN_PULL_REFRESH;// 头布局的状态: 默认为下拉刷新状态 private ListView list;//子节点中的listview视图 private LayoutParams listParam;//用于控制下拉动画展示 private boolean isLoadingMore = false;// 是否进入加载状态,防止多次重复的启动 private boolean isStart = false;//表示正在加载刷新中,还没停止 private boolean isTop = false,isBottom = false; public TwinklingRefreshLayout(Context context) { this(context, null, 0); } public TwinklingRefreshLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TwinklingRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwinklingRefreshLayout, defStyleAttr, 0); try { mHeadHeight = a.getDimensionPixelSize(R.styleable.TwinklingRefreshLayout_tr_head_height, DensityUtil.dp2px(context, 40)); } finally { a.recycle(); } addHeader(); init(); } private void addHeader() { FrameLayout headViewLayout = new FrameLayout(getContext()); FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight); this.addView(headViewLayout,layoutParams); mHeadLayout = headViewLayout; } private void init(){ initAnimation(); } @Override protected void onFinishInflate() {//布局加载成xml时触发 super.onFinishInflate(); if (mHeadView == null) setHeaderView(new HeaderView(getContext())); setFootView(); if (list == null) { list = (ListView)getChildAt(1); listParam = (LayoutParams) list.getLayoutParams(); listParam.setMargins(0, -mHeadHeight, 0, 0); list.setLayoutParams(listParam); list.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return onFingerTouch(event); } }); list.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { isTop = false; isBottom = false; //判断顶部底部 if (firstVisibleItem == 0) { Log.d(TAG, "滚动到顶部"); isTop = true; isBottom = false; } else if ((firstVisibleItem + visibleItemCount) == totalItemCount) { Log.d(TAG, "滚动到底部"); isTop = false; isBottom = true; } } }); } } /** * 设置头部View */ public void setHeaderView(final HeaderView headerView) { if (headerView != null) { post(new Runnable() { @Override public void run() { mHeadLayout.removeAllViewsInLayout(); mHeadLayout.addView(headerView.getView()); View view = LayoutInflater.from(getContext()).inflate(R.layout.item_progress,null); // mProgressBar = (ProgressBar) view.findViewById(R.id.pb_listview_header); ivArrow_left = (ImageView) view.findViewById(R.id.iv_listview_header_arrow_left) ; ivArrow_right = (ImageView) view.findViewById(R.id.iv_listview_header_arrow_right) ; mHeadLayout.addView(view); } }); mHeadView = headerView; } } /** * 设置尾部View */ public void setFootView() { mFootLayout = new FrameLayout(getContext()); View view = LayoutInflater.from(getContext()).inflate(R.layout.item_progress,null); mFootLayout.setBackgroundColor(Color.BLACK); FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight); layoutParams.gravity = Gravity.BOTTOM; this.addView(view,layoutParams); } public boolean onFingerTouch(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN : currentState = REFRESHING; downY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE : if (!isTop && !isBottom)//没有到顶,无需计算操作 break; int moveY = (int) ev.getY(); int diff = (int) (((float)moveY - (float)downY) / RATIO); int paddingTop = -mHeadLayout.getHeight() + diff; if (diff>0 && isTop) { if (paddingTop > 100 && currentState == DOWN_PULL_REFRESH) { // 完全显示了. // Log.i(TAG, "松开刷新 RELEASE_REFRESH"); currentState = RELEASE_REFRESH; refreshHeaderView(); start(); } else if (paddingTop < 0 && currentState == REFRESHING) { // 没有显示完全 // Log.i(TAG, "下拉刷新 DOWN_PULL_REFRESH"); currentState = DOWN_PULL_REFRESH; refreshHeaderView(); } if (paddingTop <= 400 && !isStart) {//已经处于运行刷新状态的时候禁止设置 listParam.setMargins(0, paddingTop, 0, 0); list.setLayoutParams(listParam); } }else if (isBottom){ if (paddingTop <= -500 && !isStart) {//已经处于运行刷新状态的时候禁止设置 listParam.setMargins(0, 0, 0, -paddingTop); list.setLayoutParams(listParam); } } Log.i(TAG,"paddingTop "+paddingTop); break; case MotionEvent.ACTION_UP : if (isLoadingMore){ isLoadingMore = false; postDelayed(new Runnable() { @Override public void run() { // Log.i(TAG, "停止 END"); // currentState = END; refreshHeaderView(); listParam.setMargins(0, -mHeadLayout.getMeasuredHeight(), 0, 0); list.setLayoutParams(listParam); stop(); } },2000); } if (currentState == DOWN_PULL_REFRESH && !isLoadingMore && !isStart) { // 隐藏头布局 listParam.setMargins(0, -mHeadLayout.getMeasuredHeight(),0,0); list.setLayoutParams(listParam); } // Log.i(TAG, "松开 REFRESHING"); currentState = REFRESHING; break; default : break; } return super.onTouchEvent(ev); } /** * 初始化动画 */ private void initAnimation() { /* * Animation.RELATIVE_TO_SELF 相对于自身的动画 * Animation.RELATIVE_TO_PARENT 相对于父控件的动画 * 0.5f,表示在控件自身的 x,y的中点坐标处,为动画的中心。 * * 设置动画的变化速率 * setInterpolator(newAccelerateDecelerateInterpolator()):先加速,后减速 * setInterpolator(newAccelerateInterpolator()):加速 * setInterpolator(newDecelerateInterpolator()):减速 * setInterpolator(new CycleInterpolator()):动画循环播放特定次数,速率改变沿着正弦曲线 * setInterpolator(new LinearInterpolator()):匀速 */ upAnimation = new RotateAnimation(0f, -180f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); upAnimation.setInterpolator(new LinearInterpolator()); upAnimation.setDuration(500); upAnimation.setFillAfter(true); // 动画结束后, 停留在结束的位置上 downAnimation = new RotateAnimation(-180f, -360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); downAnimation.setInterpolator(new LinearInterpolator());//这句话可以不写,默认匀速 downAnimation.setDuration(500); downAnimation.setFillAfter(true); // 动画结束后, 停留在结束的位置上 } /** * 根据currentState刷新头布局的状态 */ private void refreshHeaderView() { switch (currentState) { case DOWN_PULL_REFRESH : // 下拉刷新状态 // tvState.setText("下拉刷新"); ivArrow_left.startAnimation(downAnimation); // 执行向下旋转 ivArrow_right.startAnimation(downAnimation); // 执行向下旋转 break; case RELEASE_REFRESH : // 松开刷新状态 // tvState.setText("松开刷新"); ivArrow_left.startAnimation(upAnimation);// 执行向上旋转 ivArrow_right.startAnimation(upAnimation);// 执行向上旋转 break; case REFRESHING : // 正在刷新中状态 ivArrow_left.clearAnimation(); ivArrow_right.clearAnimation(); // tvState.setText("正在刷新中..."); break; default : break; } } public void start(){ isLoadingMore = true; isStart = true; mHeadView.onPullingDown(0); mHeadView.startAnim(); } public void stop(){ isLoadingMore = false; isStart = false; mHeadView.reset(); } }
有了头部还得要漂亮的动画啊,增强体验,动画就随自己配置了,本身控件的动画就是两个箭头我这里是加了个自定义的视图来做动画,绘制了几个圆圈,可以根据自己的需要调制,毕竟头部是一个小的容器布局,展示的头部也都是在这个布局里面,所以可以任意搭配
package cn.com.listwebview.demo; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import android.view.animation.DecelerateInterpolator; import cn.com.listwebview.demo.utils.DensityUtil; /** * Created by LiuZhen on 2017/3/28. */ public class HeaderView extends View { private Paint mPath; ValueAnimator animator1, animator2; private float r; float fraction1; float fraction2; boolean animating = false; private int num = 5; private int cir_x = 0; public HeaderView(Context context) { this(context, null, 0); } public HeaderView(Context context, AttributeSet attrs) { this(context, attrs,0); } public HeaderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { r = DensityUtil.dp2px(getContext(), 6); mPath = new Paint(); mPath.setAntiAlias(true); mPath.setColor(Color.rgb(114, 114, 114)); animator1 = ValueAnimator.ofFloat(1f, 1.2f, 1f, 0.8f);//从左到右过渡 animator1.setDuration(800); animator1.setInterpolator(new DecelerateInterpolator());//DecelerateInterpolator减速插补器 animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { fraction1 = (float) animation.getAnimatedValue();//监听动画运动值 invalidate(); } }); animator1.setRepeatCount(ValueAnimator.INFINITE);//设置重复次数为无限次 animator1.setRepeatMode(ValueAnimator.REVERSE);//RESTART是直接重新播放 animator2 = ValueAnimator.ofFloat(1f, 0.8f, 1f, 1.2f); animator2.setDuration(800); animator2.setInterpolator(new DecelerateInterpolator()); animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { fraction2 = (float) animation.getAnimatedValue(); } }); animator2.setRepeatCount(ValueAnimator.INFINITE); animator2.setRepeatMode(ValueAnimator.REVERSE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int w = getMeasuredWidth() / num - 10; for (int i = 0; i < num; i++) { if (animating) { switch (i) { case 0: mPath.setAlpha(105); mPath.setColor(getResources().getColor(R.color.Yellow)); canvas.drawCircle(getMeasuredWidth() / 2 - cir_x * 2 - 2 * w / 3 * 2, getMeasuredHeight() / 2, r * fraction2, mPath); break; case 1: mPath.setAlpha(145); mPath.setColor(getResources().getColor(R.color.Green)); canvas.drawCircle(getMeasuredWidth() / 2 - cir_x * 1 - w / 3 * 2, getMeasuredHeight() / 2, r * fraction2, mPath); break; case 2: mPath.setAlpha(255); mPath.setColor(getResources().getColor(R.color.Blue)); canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, r * fraction1, mPath); break; case 3: mPath.setAlpha(145); mPath.setColor(getResources().getColor(R.color.Orange)); canvas.drawCircle(getMeasuredWidth() / 2 + cir_x * 1 + w / 3 * 2, getMeasuredHeight() / 2, r * fraction2, mPath); break; case 4: mPath.setAlpha(105); mPath.setColor(getResources().getColor(R.color.Yellow)); canvas.drawCircle(getMeasuredWidth() / 2 + cir_x * 2 + 2 * w / 3 * 2, getMeasuredHeight() / 2, r * fraction2, mPath); break; } } else { switch (i) { case 0: mPath.setAlpha(105); mPath.setColor(getResources().getColor(R.color.Yellow)); canvas.drawCircle(getMeasuredWidth() / 2 - cir_x * 2 - 2 * w / 3 * 2, getMeasuredHeight() / 2, r, mPath); break; case 1: mPath.setAlpha(145); mPath.setColor(getResources().getColor(R.color.Green)); canvas.drawCircle(getMeasuredWidth() / 2 - cir_x * 1 - w / 3 * 2, getMeasuredHeight() / 2, r, mPath); break; case 2: mPath.setAlpha(255); mPath.setColor(getResources().getColor(R.color.Blue)); canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, r, mPath); break; case 3: mPath.setAlpha(145); mPath.setColor(getResources().getColor(R.color.Orange)); canvas.drawCircle(getMeasuredWidth() / 2 + cir_x * 1 + w / 3 * 2, getMeasuredHeight() / 2, r, mPath); break; case 4: mPath.setAlpha(105); mPath.setColor(getResources().getColor(R.color.Yellow)); canvas.drawCircle(getMeasuredWidth() / 2 + cir_x * 2 + 2 * w / 3 * 2, getMeasuredHeight() / 2, r, mPath); break; } } } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (animator1 != null) animator1.cancel(); if (animator2 != null) animator2.cancel(); } public View getView() { return this; } public void onPullingDown(float fraction) { setScaleX(1 + fraction / 2); setScaleY(1 + fraction / 2); animating = false; if (animator1.isRunning()) { animator1.cancel(); invalidate(); } if (animator2.isRunning()) animator2.cancel(); } public void startAnim() { animating = true; if (!animator1.isRunning()) animator1.start(); if (!animator2.isRunning()) animator2.start(); } public void reset() { animating = false; if (animator1.isRunning()) animator1.cancel(); if (animator2.isRunning()) animator2.cancel(); invalidate(); } }
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/iv_listview_header_arrow_left" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:minWidth="30dip" android:visibility="visible" android:src="@drawable/common_listview_headview_red_arrow" /> <ImageView android:id="@+id/iv_listview_header_arrow_right" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|right" android:minWidth="30dip" android:visibility="visible" android:src="@drawable/common_listview_headview_red_arrow" /> <!--<!– android:indeterminateDrawable="@drawable/common_progressbar",--> <!--利用rotate旋转动画 + shape的颜色变化 构造ProgressBar的旋转颜色 –>--> <!--<ProgressBar--> <!--android:padding="10dp"--> <!--android:id="@+id/pb_listview_header"--> <!--android:layout_width="wrap_content"--> <!--android:layout_height="wrap_content"--> <!--android:layout_gravity="center_vertical"--> <!--style="?android:attr/progressBarStyleSmallInverse"--> <!--android:visibility="gone" />--> </FrameLayout>
事实证明,这种写法很简单方便,特别是使用上,套个壳就ok了,什么都不用做,只要写控件的时候处理好就行了,开源库可以学到很多东西,各种好的封装,写法,让我获益良多,完善了在后续更新了。
listview下拉刷新