一步步实现带动画效果的下拉刷新
来源:互联网 发布:方舟画质优化 编辑:程序博客网 时间:2024/05/21 06:39
先看效果
分析
1.先要在listview的头部加上一个布局,布局中包含一个文本控件一个图片
2.这个图片控件会随着下拉的过程做一个缩放
3.整个下拉刷新过程分三步:
第一步:下拉未超过布局的原始高度,图片做缩放动作,文字显示下拉刷新
第二步:下拉超过布局的原始高度,图片大小保持不变,文字显示松开刷新
第三步:松手后,如果当前位置在原始高度的上方,不进行刷新,直接回弹;如果在下方,执行刷新任务,并播放动画效果,完成后回弹
实战
1.我们需要一个能随着滑动改变自身大小的自定义控件,继承view是个不错的选择
2.通过画布的缩放来控制图片的大小,同时别忘了处理padding
@Overrideprotected 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 { 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(); }}5.开始实现继承listview的PullRefreshListView。先加载布局
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的显示程度
@Overridepublic 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 { 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(); }}10.试一下
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
4 0
- 一步步实现带动画效果的下拉刷新
- 一步步实现带动画效果的下拉刷新
- Android实现带动画的下拉刷新RecyclerView
- Android实现带动画的下拉刷新RecyclerView
- 使用android自带的下拉刷新效果实现页面下拉刷新功能
- Android的下拉刷新带进度条效果
- MUI添加下拉刷新的动画效果
- android google 自带的下拉刷新 类似nice的加载数据动画效果
- 探索Android 下拉刷新效果的实现
- 探索Android 下拉刷新效果的实现
- 自定义listview实现下拉刷新的效果
- 带头尾和动画的下拉刷新RecyclerView
- Android之---PullRefreshListView(带抖动效果的下拉刷新)
- iOS6的Mail里下拉刷新动画效果是什么原理
- MJRefresh实现动画下拉刷新
- UIScrollView下拉刷新效果实现
- Android下拉刷新效果实现
- android 自定义下拉刷新动画效果
- 在Springmvc中导出报表下载Excel文件
- vc++经典技巧总结
- EasyUI之Combobox 数据加载完之后执行
- CodeForces 471A
- 一次旅途的启程
- 一步步实现带动画效果的下拉刷新
- 关于iOS提交审核的IDFA
- 扣丁学堂——显示网络图片到手机中
- 安卓DisplayMetrics获取屏幕分辨率
- HDFS HA与QJM(Quorum Journal Manager)介绍及官网内容整理
- 终于发布了一个app游戏
- 基于数码管的简单时钟系统
- ubuntu 14.04中打开echo、daytime等标准服务详细实例图解
- Android ListView头部随着上下拖动缩放