一步步实现带动画效果的下拉刷新
2016-01-14 10:42
429 查看
先看效果
分析
1.先要在listview的头部加上一个布局,布局中包含一个文本控件一个图片
2.这个图片控件会随着下拉的过程做一个缩放
3.整个下拉刷新过程分三步:
第一步:下拉未超过布局的原始高度,图片做缩放动作,文字显示下拉刷新
第二步:下拉超过布局的原始高度,图片大小保持不变,文字显示松开刷新
第三步:松手后,如果当前位置在原始高度的上方,不进行刷新,直接回弹;如果在下方,执行刷新任务,并播放动画效果,完成后回弹
实战
1.我们需要一个能随着滑动改变自身大小的自定义控件,继承view是个不错的选择
2.通过画布的缩放来控制图片的大小,同时别忘了处理padding
@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(); }3.需要一个方法供外部调用,用于控制绘制控件的大小
public void setCurrentProgress(float currentProgress) { mCurrentProgress = currentProgress; postInvalidate(); }4.完整的代码
public class ScaleView extends View {5.开始实现继承listview的PullRefreshListView。先加载布局
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.drawable.bell));
}
@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(); }
}
mHeadView = LayoutInflater.from(context).inflate(R.layout.item_headview, null, false); loadMoreView = (ScaleView) mHeadView.findViewById(R.id.loadMoreView); tv = (TextView) mHeadView.findViewById(R.id.tv); addHeaderView(mHeadView);6.根据下拉的距离,控制headview的状态。靠listView的paddingTop来控制headView的显示程度
@Override public boolean onTouchEvent(MotionEvent ev) { if(!refreshEnable || isAnimatoring) { return super.onTouchEvent(ev); } y = ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: //下拉,不超过原始的布局高度 if (mfirstVisibleItem == 0 && y > mLastY && offsetY < 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(loadMoreView); post(new Runnable() { @Override public void run() { mOnPullRefreshListener.onRefresh(); } }); } else { resetState(); } break; } mLastY = y; return super.onTouchEvent(ev); }7.抖动的动画其实就是x方向的来回位移
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; }8.提供监听供调用刷新任务,同时提供任务完成的终止方法
/** * 刷新完成 */ 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(); }9.完整的源码
public class PullRefreshListView extends ListView implements AbsListView.OnScrollListener {10.试一下
private ScaleView loadMoreView;
private TextView tv;
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);
//先把布局加载进来
mHeadView = LayoutInflater.from(context).inflate(R.layout.item_headview, null, false); loadMoreView = (ScaleView) mHeadView.findViewById(R.id.loadMoreView); tv = (TextView) mHeadView.findViewById(R.id.tv); addHeaderView(mHeadView);
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(loadMoreView);
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;
tv.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) {
tv.setText("松开刷新");
} else {
tv.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;
loadMoreView.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(); }
}
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); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); 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(String.valueOf(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(); } }
总结
1.headview的位置变化没有使用弹性滑动,可以完善
2.可以在刷新阶段加入更多酷炫的动画
3.上拉加载后边加上
4.后边用RecyclerView来实现下
源码地址:https://github.com/wolow3/PullRefreshListView
相关文章推荐
- 扣丁学堂——显示网络图片到手机中
- HDFS HA与QJM(Quorum Journal Manager)介绍及官网内容整理
- getSupportFragmentManager().findFragmentById()返回为null
- 程序猿的量化交易之路(29)--Cointrader之Tick实体(16)
- javascript中数组去重的4种方法
- array类型的方法
- Android开发之MVP模式
- sublime text 的使用技巧
- C/C++中extern关键字详解
- EasyUI之Combobox 数据加载完之后执行
- vc++经典技巧总结
- 在Springmvc中导出报表下载Excel文件
- UI组件之ImageView及其子类(二)ImageButton ,ZoomButton
- python3.x与2.x区别
- 【FlexPaper】参数以及使用
- Shell脚本编程之判断语句
- MongoDB 创建新用户并授权
- Swift2中的String与Character
- java配置数据库连接池的方法步骤
- Android 之 传感器 应用