PullRefreshListView(带抖动效果的下拉刷新)
效果图
代码实现
1.首先实现下拉抖动的自定义的view(可以是铃铛、锣鼓啊什么的图片)
ScaleView.java:
package com.xiaokele.pullrefreshlistview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
/**
*
* 自定义的缩放的View
*/
public class ScaleView extends View {
private Bitmap initBitmap;
private Bitmap scaleBitmap;
private float mCurrentProgress = 1;
private int mWidth;
private int mHeight;
public ScaleView(Context context) {
super(context);
init(context);
}
public ScaleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ScaleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
initBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mWidth = widthSize;
} else {
mWidth = initBitmap.getWidth();
}
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
} else {
mHeight = initBitmap.getHeight();
}
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//考虑padding的影响
int leftPadding = getPaddingLeft();
int topPadding = getPaddingTop();
int rightPadding = getPaddingRight();
int bottomPadding = getPaddingBottom();
int lastWidth = getMeasuredWidth() - leftPadding - rightPadding;
int lastHeight = getMeasuredHeight() - topPadding - bottomPadding;
scaleBitmap = Bitmap.createScaledBitmap(initBitmap, lastWidth, lastHeight, true);
canvas.save();
//缩放画布
canvas.scale(mCurrentProgress, mCurrentProgress, lastWidth / 2 + leftPadding, lastHeight / 2 + topPadding);
//缩放图形,要写在画布缩放后边
canvas.drawBitmap(scaleBitmap, topPadding, leftPadding, null);
canvas.restore();
}
public void setCurrentProgress(float currentProgress) {
mCurrentProgress = currentProgress;
postInvalidate();
}
}
2.接下来是实现自定义的带下拉效果的ListView(PullRefreshListView)
添加的HeaderView布局sample_pull_refresh_list_view.xml如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:gravity="center"
android:orientation="horizontal"
android:layout_height="match_parent">
<com.xiaokele.pullrefreshlistview.ScaleView
android:id="@+id/scaleView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:gravity="center"
android:text="下拉刷新"
android:textColor="@android:color/black"
android:textSize="16sp" />
</LinearLayout>
PullRefreshListView.java如下:
package com.xiaokele.pullrefreshlistview;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.TextView;
/**
* TODO: document your custom view class.
*/
public class PullRefreshListView extends ListView implements AbsListView.OnScrollListener {
private ScaleView scaleView;
private TextView textView;
private View mHeadView;
private int mHeadViewHeight;
private float mLastY, y, offsetY;
private int mfirstVisibleItem;
/**
* 动画播放时间
*/
private static final int ANIM_DURATION = 200;
/**
* 缩小滑动时对padding的影响
*/
private static final int RESISTANCE = 3;
/**
* 是否实现下拉刷新接口
*/
private boolean refreshEnable = false;
/**
* 是否在播放动画
*/
private boolean isAnimatoring = false;
/**
* 下拉刷新回调接口
*/
private OnPullRefreshListener mOnPullRefreshListener;
/**
* 刷新动画
*/
private ObjectAnimator mObjectAnimator;
public PullRefreshListView(Context context) {
super(context);
init(context);
}
public PullRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public PullRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
setOverScrollMode(OVER_SCROLL_NEVER);
//先把布局加载进来(**就是上面的sample_pull_refresh_list_view.xml**)
mHeadView = LayoutInflater.from(context).inflate(R.layout.sample_pull_refresh_list_view, null, false);
scaleView = (ScaleView) mHeadView.findViewById(R.id.scaleView);
textView = (TextView) mHeadView.findViewById(R.id.textView);
addHeaderView(mHeadView);
super.post(new Runnable() {
@Override
public void run() {
//把headView的高度取出来
mHeadViewHeight = mHeadView.getMeasuredHeight();
resetState();
}
});
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!refreshEnable || isAnimatoring) {
return super.onTouchEvent(ev);
}
y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
//下拉,最多下拉到2倍高度的位置
if (mfirstVisibleItem == 0 && y > mLastY && offsetY < 2 * mHeadViewHeight) {
changState();
}
//上滑
if (mfirstVisibleItem == 0 && y < mLastY && offsetY > 0) {
changState();
}
break;
case MotionEvent.ACTION_UP:
int curPaddingTop = getPaddingTop();
if (curPaddingTop > 0) {
isAnimatoring = true;
refreshingState();
mObjectAnimator = startRefreshAnim(scaleView);
post(new Runnable() {
@Override
public void run() {
mOnPullRefreshListener.onRefresh();
}
});
} else {
resetState();
}
break;
}
mLastY = y;
return super.onTouchEvent(ev);
}
/**
* 正在刷新的状态
*/
private void refreshingState() {
setHeadViewPadding(mHeadViewHeight);
setCurrentProgress(mHeadViewHeight);
offsetY = mHeadViewHeight;
textView.setText("正在刷新");
}
/**
* 将状态设置回原始状态
*/
private void resetState() {
offsetY = 0;
setHeadViewPadding(0);
setCurrentProgress(0);
}
/**
* 滑动时动态设置各个组件的状态
*/
private void changState() {
offsetY = offsetY + (y - mLastY) / RESISTANCE;
setHeadViewPadding((int) (offsetY));
//从二分之一的地方开始缩放,使缩放效果更明显
if (offsetY > mHeadViewHeight / 2) {
setCurrentProgress((offsetY - mHeadViewHeight / 2) * 2);
}
//设置字体状态
if (offsetY > mHeadViewHeight) {
textView.setText("松开刷新");
} else {
textView.setText("下拉刷新");
}
}
/**
* 播放刷新动画
*
* @param target
*/
private ObjectAnimator startRefreshAnim(ScaleView target) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(target, View.TRANSLATION_X, 0, 20, 0, -20, 0);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.setInterpolator(new DecelerateInterpolator());
objectAnimator.setDuration(ANIM_DURATION);
objectAnimator.start();
return objectAnimator;
}
/**
* 根据滑动的距离设置图片的缩放
*
* @param offsetY
*/
private void setCurrentProgress(float offsetY) {
float scale = offsetY / mHeadViewHeight;
scale = scale > 1 ? 1 : scale;
scaleView.setCurrentProgress(scale);
}
/**
* 位移相对于隐藏headview原点
*
* @param offset
*/
private void setHeadViewPadding(int offset) {
setPadding(0, offset - mHeadViewHeight, 0, 0);
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mfirstVisibleItem = firstVisibleItem;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
/**
* 刷新完成
*/
public void complete() {
mObjectAnimator.cancel();
resetState();
isAnimatoring = false;
}
/**
* 设置刷新回调监听
*
* @param onPullRefreshListener
*/
public void setOnPullRefreshListener(OnPullRefreshListener onPullRefreshListener) {
if (onPullRefreshListener == null) {
return;
}
this.mOnPullRefreshListener = onPullRefreshListener;
refreshEnable = true;
}
public interface OnPullRefreshListener {
void onRefresh();
}
}
3.PullRefreshListView的使用
activity_main.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.xiaokele.pullrefreshlistview.MainActivity">
<com.xiaokele.pullrefreshlistview.PullRefreshListView
android:id="@+id/pullRefreshListView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
MainActivity.java如下:
package com.xiaokele.pullrefreshlistview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity implements PullRefreshListView.OnPullRefreshListener {
private List<String> mDatas;
private ArrayAdapter<String> mAdapter;
private PullRefreshListView mPullRefreshListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
try {
mPullRefreshListView = (PullRefreshListView) findViewById(R.id.pullRefreshListView);
initData();
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mDatas);
mPullRefreshListView.setOnPullRefreshListener(this);
mPullRefreshListView.setAdapter(mAdapter);
} catch (Exception e) {
e.printStackTrace();
}
}
private void initData() {
mDatas = new ArrayList<>();
for (int i = 0; i < 20; i++) {
mDatas.add("xiaokele"+i);
}
}
@Override
public void onRefresh() {
Toast.makeText(MainActivity.this, "success", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时任务
Thread.sleep(3000);
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
//任务执行完毕
mPullRefreshListView.complete();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}