文章的开头奉送上代码,方便大家对照学习。
这二天写了一个仿美团的下拉刷新效果,效果图如下:
怎么样还不错吧,下面我就教大家做。这个做的还是太粗糙,学习还行,用在项目中还要再改改。
1.用到的知识点:
其实这个做起来出没那么难,主要用到的知识点如下:
1.Scroller的使用,这个主要有来松手时的回滚效果。
2.onTouchEvent重写
3.自定义控件的相关知识。
如果大家不会Scroller,请翻阅这一篇文章。
2.思路流程
下面来说一下整体思路:
1、建一个类MyListView继承listView。
2.建一个类头部的View(HeaderView),并通过addHeaderView()和Mylistview相关联。
2、重写onTouchEvent,在滑动的时候不断计算滑动的距离,并分下列2种情况:
a.如果滑动距离<60,当放开手指时,应该回滚到初始位置,回滚是利用Scrller做的。
b.如果滑动距离>60,这个时候触发第一步动画(小孩有躺着到直立的动画),当松开手指时,应该回滚到60的位置上,并触发第三个动画(小孩不断的摇晃身子的动画)。
3.在滑动的过程中,头部HeaderView的高度要不断的随着滑动的距离增加而增加,而且要不断的控制小孩所在的imageview的大小。
整体的思路就是这么简单,大家是不是豁然开朗。来看一下小孩有躺着到直立的动画是怎么实现的:
这是一个帧动画,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true" >
<item android:drawable="@drawable/pull_end_image_frame_01" android:duration="100"/>
<item android:drawable="@drawable/pull_end_image_frame_02" android:duration="100"/>
<item android:drawable="@drawable/pull_end_image_frame_03" android:duration="100"/>
<item android:drawable="@drawable/pull_end_image_frame_04" android:duration="100"/>
<item android:drawable="@drawable/pull_end_image_frame_05" android:duration="100"/>
</animation-list>
使用方式如下:
refreshImg.setBackgroundResource(R.drawable.pull_to_refresh_second_anim);
secondAnim = (AnimationDrawable) refreshImg.getBackground();// 启动
secondAnim.start();
其实第二步的动画(小孩不断摇晃)的动画也是一样的道理,无非图片不一样而已。
3.详细实现
我的代码编写是在eclipse上编写的,不用AS的原因是因为AS太卡了,不如eclipse来的方便,如果有不方便的地方,请大家见谅。
3.1HeadView的布局文件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:background="#F0F0F0"
android:padding="5dp"
android:layout_height="0dp"
>
<ImageView
android:id="@+id/refreshImg"
android:background="@drawable/pull_image"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
/>
</RelativeLayout>
为个布局文件中,之所以要用RelativeLayout,是要保证ImageView始终在RelativeLayout的底部。
3.2HeadView类代码如下
public class HeaderView extends RelativeLayout {
private View view;
private ImageView refreshImg;
private AnimationDrawable secondAnim, threeAnim;
private int LISTVIEW_STATE = MyListView.STATE_NORMAL;// 当前listview处于什么状态,刷新和平常2中状态
public HeaderView(Context context) {
super(context);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, 0);
view = (View) LayoutInflater.from(context).inflate(
R.layout.header_layout, null);
refreshImg = (ImageView) view.findViewById(R.id.refreshImg);
addView(view, lp);
}
public void setListViewState(int state) {
LISTVIEW_STATE = state;
}
/**
* 这个方法什么都不用管,只管设置高度。
*
* @param height
*/
public void setVisiableHeight(int height) {
if (height < 0) {
height = 0;
}
if (LISTVIEW_STATE == MyListView.STATE_NORMAL) {
setImgCal((height<30)?height:30, 82, 60);
} else if (LISTVIEW_STATE == MyListView.STATE_FRESH) {
setImgCal(MyListView.RefreshDistance - getPaddingBottom() - getPaddingTop(), 82, 107);
}
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) view.getLayoutParams();
lp.height = height;
view.setLayoutParams(lp);
}
public int getVisiableHeight() {
return view.getHeight();
}
//根据高度,按照比例设置图片的高和宽
private void setImgCal(int heigth, int scaleW, int scaleH) {
int width = (heigth * scaleW) / scaleH;
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) refreshImg
.getLayoutParams();
lp.height = heigth;
lp.width = width;
refreshImg.setLayoutParams(lp);
}
/**
* 设置第二个阶段,当拉来一定的高度后,就设置一个小孩从躺着到直立的动画。
*/
public void setSecondState() {
refreshImg
.setBackgroundResource(R.drawable.pull_to_refresh_second_anim);
secondAnim = (AnimationDrawable) refreshImg.getBackground();// 启动
secondAnim.start();
}
/**
* 刷新阶段,一个小孩在左右晃的图片
*/
public void setThirdState() {
refreshImg.setBackgroundResource(R.drawable.pull_to_refresh_third_anim);
threeAnim = (AnimationDrawable) refreshImg.getBackground();// 启动
threeAnim.start();
}
/**
* 设置成初始图片
*/
public void setNormalState() {
refreshImg.setBackgroundResource(R.drawable.pull_image);
secondAnim.stop();
threeAnim.stop();
}
}
setSecondState()
setThirdState()
setNormalState()
这三个方法是设置HeaderView 要显示那种动画的。
setVisiableHeight()方法是根据滑动的距离来设置HeaderView的高度的。
setImgCal(int heigth, int scaleW, int scaleH)是用来根据高度,按比例计算图片宽度并设置。例如真实图片的宽和高是82和60,如果滑动的距离是30,那么图片的宽度是:(30 * 82) / 60。这样就会保证不被拉伸。
3.3核心类MyListView类代码如下 :
**
* @author 作者 YYD
* @version 创建时间:2016年12月23日 上午11:20:17
* @function 未添加
*/
@SuppressLint("NewApi")
public class MyListView extends ListView {
/**
* 本接口用来实现下拉刷新和滑动加载的数据加载
*/
public interface MListViewListener {
public void onRefresh();
public void onLoadMore();
}
private MListViewListener mListViewListener;
public void setmListViewListener(MListViewListener mListViewListener) {
this.mListViewListener = mListViewListener;
}
/**
* 调用接口刷新数据的方法
* */
private void startRefresh() {
if (mListViewListener != null) {
mListViewListener.onRefresh();
}
}
/**
* 调用接口加载数据的方法
* */
private void startLoadMore() {
if (mListViewListener != null) {
mListViewListener.onLoadMore();
}
}
public static final int STATE_NORMAL = 1000;// 平常状态,包括普通和刷新结束
public static final int STATE_FRESH = 1001;// 刷新的状态
private int LISTVIEW_STATE = STATE_NORMAL;// 当前listview处于什么状态,刷新和平常2中状态
public static final int RefreshDistance = 60;
private float lastY = -1;// 记录最近一次的偏移量
private final float OFFSET_RADIO = 1.8f;// 灵敏度
private final int SCROLL_TIME = 300;// 动画返回的时间
private HeaderView headerView;// 头部view
private Scroller mScroller;// 下拉后返回的Scroller
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context, new OvershootInterpolator());
headerView = new HeaderView(context);
addHeaderView(headerView);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = ev.getRawY();
break;
// 用户滑动
case MotionEvent.ACTION_MOVE:
if (lastY == -1) {
lastY = ev.getRawY();
}
float distance = ev.getRawY() - lastY;
lastY = ev.getRawY();
moveState(distance);
Log.d("refreshlistview", "" + distance);
break;
// 当用户手指抬起时
case MotionEvent.ACTION_UP:
upHeightBack();
break;
}
return super.onTouchEvent(ev);
}
private void moveState(float distance) {
if (getFirstVisiblePosition() == 0
&& (headerView.getVisiableHeight() > 0 || lastY > 0)) {
// 如果当前是第一项并且头部视图可见高度大于0或者Y轴移动距离大于0(证明是从上往下拉的),更新头部视图
distance /= OFFSET_RADIO;
distance += headerView.getVisiableHeight();
if (distance < RefreshDistance) {
setListViewState(STATE_NORMAL);
} else {
if(LISTVIEW_STATE != STATE_FRESH){
headerView.setSecondState();
setListViewState(STATE_FRESH);
startRefresh();
}
}
updateHeaderHeight(distance);
}
}
public void setListViewState(int state) {
headerView.setListViewState(state);
this.LISTVIEW_STATE = state;
}
/**
* MotionEvent.ACTION_UP 的时候,弹回去。
*/
private void upHeightBack() {
int height = headerView.getVisiableHeight();
if (height == 0) {
// 如果已经是原始位置,return
return;
}
int scrollHeigth = 0;
if (LISTVIEW_STATE == STATE_NORMAL) {
scrollHeigth = 0 - height;
} else if (LISTVIEW_STATE == STATE_FRESH) {
scrollHeigth = MyListView.RefreshDistance - height;
headerView.setThirdState();
}
// 松开刷新时,如果头部视图可见高度大于头部视图的实际高度,只显示头部视图实际的高度,
mScroller.startScroll(0, height, 0, scrollHeigth, SCROLL_TIME);
invalidate(); // 刷新视图(在UI线程中使用,在非UI线程中应该使用postInvalidate)
}
/**
* 更新头部视图
*/
private void updateHeaderHeight(float delta) {
headerView.setVisiableHeight((int) delta);
setSelection(0); // 恢复ListView原始的位置
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
// 滑动完成
headerView.setVisiableHeight(mScroller.getCurrY());
postInvalidate();// 刷新视图(在非UI线程中使用,在UI线程中应该使用invalidate)
}
super.computeScroll();
}
/**
* 停止刷新
*/
public void stopRefresh() {
LISTVIEW_STATE = STATE_NORMAL;
headerView.setNormalState();
updateHeaderHeight(0);
}
}
这里对这个类做一下解释如下:
MListViewListener 是回调接口,上下拉的时候会分别回调onRefresh()和onLoadMore()方法。
LISTVIEW_STATE是ListView的状态分2种:普通和正在刷新的状态。
mScroller是回滚时用的,如果不懂可以参考我前面写的博客。
onTouchEvent是核心方法,里面监听:手指按下,抬起,滑动的事件并做处理。
moveState(float distance)是处理滑动的逻辑,滑动期间要不断的通过updateHeaderHeight()改变HeaderView的高度。
upHeightBack()是手指抬起来的时候要回滚,注意区别普通状态和刷新状态。
文章的结尾奉送上代码,方便大家对照学习。
结尾
好了就讲到这里吧,整体的逻辑不是不难的。因为明天还要做别的事情,这个半成品只能暂且搁置,先做一个记录吧,等我有空再整理整理。希望对大家有所帮助。
在技术上我依旧是个小渣渣,加油勉励自己!