最近要做一个下拉刷新的功能,网上找了很多例子,也看了一些开源的下拉刷新项目,但是小例子比较简单,效果和稳定性都差强人意,而开源的项目又太庞大,看起来耗时费劲,所以只好综合一下各处的代码掌握其原理,自己实现一套下拉刷新功能。
该控件特点:
1.子控件必须是一个ScrollView或ListView;
2.支持自定义下拉布局;
3.自定义下拉布局可以不用处理下拉的各种状态(只需要实现几个接口即可),也可以自己处理各种下拉的状态。
先来看看效果图:
上代码:
首先看如何使用:
1.使用的布局:
<LinearLayout 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:orientation="vertical" >
<com.example.pulldown.PullDownScrollView
android:id="@+id/refresh_root"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:background="#161616"
android:orientation="vertical" >
<ScrollView
android:id="@+id/scrollview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="none" >
<LinearLayout
android:id="@+id/mainView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#1f1f1f"
android:orientation="vertical" >
<!-- 自已的布局 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:gravity="center"
android:text="@string/hello_world"
android:textColor="@android:color/white"
android:textSize="18sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:gravity="center"
android:text="@string/hello_world"
android:textColor="@android:color/white"
android:textSize="18sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:gravity="center"
android:text="@string/hello_world"
android:textColor="@android:color/white"
android:textSize="18sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:gravity="center"
android:text="@string/hello_world"
android:textColor="@android:color/white"
android:textSize="18sp" />
</LinearLayout>
</ScrollView>
</com.example.pulldown.PullDownScrollView>
</LinearLayout>
2.UI使用:
首先,Activity实现接口:
implements RefreshListener
部分代码如下:
package com.example.pulldown;
import com.example.pulldown.PullDownScrollView.RefreshListener;
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity implements RefreshListener{
private PullDownScrollView mPullDownScrollView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPullDownScrollView = (PullDownScrollView) findViewById(R.id.refresh_root);
mPullDownScrollView.setRefreshListener(this);
mPullDownScrollView.setPullDownElastic(new PullDownElasticImp(this));
}
@Override
public void onRefresh(PullDownScrollView view) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
mPullDownScrollView.finishRefresh("上次刷新时间:12:23");
}
}, 2000);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
3.再来看看控件代码:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
/**
* @author xwangly@163.com
* @date 2013-7-9
*
*/
public class PullDownScrollView extends LinearLayout {
private static final String TAG = "PullDownScrollView";
private int refreshTargetTop = -60;
private int headContentHeight;
private RefreshListener refreshListener;
private RotateAnimation animation;
private RotateAnimation reverseAnimation;
private final static int RATIO = 2;
private int preY = 0;
private boolean isElastic = false;
private int startY;
private int state;
private String note_release_to_refresh = "松开更新";
private String note_pull_to_refresh = "下拉刷新";
private String note_refreshing = "正在更新...";
private IPullDownElastic mElastic;
public PullDownScrollView(Context context) {
super(context);
init();
}
public PullDownScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
animation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
animation.setInterpolator(new LinearInterpolator());
animation.setDuration(250);
animation.setFillAfter(true);
reverseAnimation = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
reverseAnimation.setInterpolator(new LinearInterpolator());
reverseAnimation.setDuration(200);
reverseAnimation.setFillAfter(true);
}
/**
* 刷新监听
* @param listener
*/
public void setRefreshListener(RefreshListener listener) {
this.refreshListener = listener;
}
/**
* 下拉布局
* @param elastic
*/
public void setPullDownElastic(IPullDownElastic elastic) {
mElastic = elastic;
headContentHeight = mElastic.getElasticHeight();
refreshTargetTop = - headContentHeight;
LayoutParams lp = new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT, headContentHeight);
lp.topMargin = refreshTargetTop;
addView(mElastic.getElasticLayout(), 0, lp);
}
/**
* 设置更新提示语
* @param pullToRefresh 下拉刷新提示语
* @param releaseToRefresh 松开刷新提示语
* @param refreshing 正在刷新提示语
*/
public void setRefreshTips(String pullToRefresh, String releaseToRefresh, String refreshing) {
note_pull_to_refresh = pullToRefresh;
note_release_to_refresh = releaseToRefresh;
note_refreshing = refreshing;
}
/*
* 该方法一般和ontouchEvent 一起用 (non-Javadoc)
*
* @see
* android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent)
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Logger.d(TAG, "onInterceptTouchEvent");
printMotionEvent(ev);
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
preY = (int) ev.getY();
}
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
Logger.d(TAG, "isElastic:" + isElastic + " canScroll:"+ canScroll() + " ev.getY() - preY:"+(ev.getY() - preY));
if (!isElastic && canScroll()
&& (int) ev.getY() - preY >= headContentHeight / (3*RATIO)
&& refreshListener != null && mElastic != null) {
isElastic = true;
startY = (int) ev.getY();
Logger.i(TAG, "在move时候记录下位置startY:" + startY);
return true;
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Logger.d(TAG, "onTouchEvent");
printMotionEvent(event);
handleHeadElastic(event);
return super.onTouchEvent(event);
}
private void handleHeadElastic(MotionEvent event) {
if (refreshListener != null && mElastic != null) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Logger.i(TAG, "down");
break;
case MotionEvent.ACTION_UP:
Logger.i(TAG, "up");
if (state != IPullDownElastic.REFRESHING && isElastic) {
if (state == IPullDownElastic.DONE) {
// 什么都不做
setMargin(refreshTargetTop);
}
if (state == IPullDownElastic.PULL_To_REFRESH) {
state = IPullDownElastic.DONE;
setMargin(refreshTargetTop);
changeHeaderViewByState(state, false);
Logger.i(TAG, "由下拉刷新状态,到done状态");
}
if (state == IPullDownElastic.RELEASE_To_REFRESH) {
state = IPullDownElastic.REFRESHING;
setMargin(0);
changeHeaderViewByState(state, false);
onRefresh();
Logger.i(TAG, "由松开刷新状态,到done状态");
}
}
isElastic = false;
break;
case MotionEvent.ACTION_MOVE:
Logger.i(TAG, "move");
int tempY = (int) event.getY();
if (state != IPullDownElastic.REFRESHING && isElastic) {
// 可以松手去刷新了
if (state == IPullDownElastic.RELEASE_To_REFRESH) {
if (((tempY - startY) / RATIO < headContentHeight)
&& (tempY - startY) > 0) {
state = IPullDownElastic.PULL_To_REFRESH;
changeHeaderViewByState(state, true);
Logger.i(TAG, "由松开刷新状态转变到下拉刷新状态");
} else if (tempY - startY <= 0) {
state = IPullDownElastic.DONE;
changeHeaderViewByState(state, false);
Logger.i(TAG, "由松开刷新状态转变到done状态");
}
}
if (state == IPullDownElastic.DONE) {
if (tempY - startY > 0) {
state = IPullDownElastic.PULL_To_REFRESH;
changeHeaderViewByState(state, false);
}
}
if (state == IPullDownElastic.PULL_To_REFRESH) {
// 下拉到可以进入RELEASE_TO_REFRESH的状态
if ((tempY - startY) / RATIO >= headContentHeight) {
state = IPullDownElastic.RELEASE_To_REFRESH;
changeHeaderViewByState(state, false);
Logger.i(TAG, "由done或者下拉刷新状态转变到松开刷新");
} else if (tempY - startY <= 0) {
state = IPullDownElastic.DONE;
changeHeaderViewByState(state, false);
Logger.i(TAG, "由DOne或者下拉刷新状态转变到done状态");
}
}
if (tempY - startY > 0) {
setMargin((tempY - startY)/2 + refreshTargetTop);
}
}
break;
}
}
}
/**
*
*/
private void setMargin(int top) {
LinearLayout.LayoutParams lp = (LayoutParams) mElastic.getElasticLayout()
.getLayoutParams();
lp.topMargin = top;
// 修改后刷新
mElastic.getElasticLayout().setLayoutParams(lp);
mElastic.getElasticLayout().invalidate();
}
private void changeHeaderViewByState(int state, boolean isBack) {
mElastic.changeElasticState(state, isBack);
switch (state) {
case IPullDownElastic.RELEASE_To_REFRESH:
mElastic.showArrow(View.VISIBLE);
mElastic.showProgressBar(View.GONE);
mElastic.showLastUpdate(View.VISIBLE);
mElastic.setTips(note_release_to_refresh);
mElastic.clearAnimation();
mElastic.startAnimation(animation);
Logger.i(TAG, "当前状态,松开刷新");
break;
case IPullDownElastic.PULL_To_REFRESH:
mElastic.showArrow(View.VISIBLE);
mElastic.showProgressBar(View.GONE);
mElastic.showLastUpdate(View.VISIBLE);
mElastic.setTips(note_pull_to_refresh);
mElastic.clearAnimation();
// 是由RELEASE_To_REFRESH状态转变来的
if (isBack) {
mElastic.startAnimation(reverseAnimation);
}
Logger.i(TAG, "当前状态,下拉刷新");
break;
case IPullDownElastic.REFRESHING:
mElastic.showArrow(View.GONE);
mElastic.showProgressBar(View.VISIBLE);
mElastic.showLastUpdate(View.GONE);
mElastic.setTips(note_refreshing);
mElastic.clearAnimation();
Logger.i(TAG, "当前状态,正在刷新...");
break;
case IPullDownElastic.DONE:
mElastic.showProgressBar(View.GONE);
mElastic.clearAnimation();
// arrowImageView.setImageResource(R.drawable.goicon);
// tipsTextview.setText("下拉刷新");
// lastUpdatedTextView.setVisibility(View.VISIBLE);
Logger.i(TAG, "当前状态,done");
break;
}
}
private void onRefresh() {
// downTextView.setVisibility(View.GONE);
// scroller.startScroll(0, i, 0, 0 - i);
// invalidate();
if (refreshListener != null) {
refreshListener.onRefresh(this);
}
}
/**
*
*/
@Override
public void computeScroll() {
// if (scroller.computeScrollOffset()) {
// int i = this.scroller.getCurrY();
// LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) this.refreshView
// .getLayoutParams();
// int k = Math.max(i, refreshTargetTop);
// lp.topMargin = k;
// this.refreshView.setLayoutParams(lp);
// this.refreshView.invalidate();
// invalidate();
// }
}
/**
* 结束刷新事件,UI刷新完成后必须回调此方法
* @param text 一般传入:“上次更新时间:12:23”
*/
public void finishRefresh(String text) {
if (mElastic == null) {
Logger.d(TAG, "finishRefresh mElastic:" + mElastic);
return;
}
state = IPullDownElastic.DONE;
mElastic.setLastUpdateText(text);
changeHeaderViewByState(state,false);
Logger.i(TAG, "执行了=====finishRefresh");
mElastic.showArrow(View.VISIBLE);
mElastic.showLastUpdate(View.VISIBLE);
setMargin(refreshTargetTop);
// scroller.startScroll(0, i, 0, refreshTargetTop);
// invalidate();
}
private boolean canScroll() {
View childView;
if (getChildCount() > 1) {
childView = this.getChildAt(1);
if (childView instanceof AbsListView) {
int top = ((AbsListView) childView).getChildAt(0).getTop();
int pad = ((AbsListView) childView).getListPaddingTop();
if ((Math.abs(top - pad)) < 3
&& ((AbsListView) childView).getFirstVisiblePosition() == 0) {
return true;
} else {
return false;
}
} else if (childView instanceof ScrollView) {
if (((ScrollView) childView).getScrollY() == 0) {
return true;
} else {
return false;
}
}
}
return canScroll(this);
}
/**
* 子类重写此方法可以兼容其它的子控件,目前只兼容AbsListView和ScrollView
* @param view
* @return
*/
public boolean canScroll(PullDownScrollView view) {
return false;
}
private void printMotionEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Logger.d(TAG, "down");
break;
case MotionEvent.ACTION_MOVE:
Logger.d(TAG, "move");
break;
case MotionEvent.ACTION_UP:
Logger.d(TAG, "up");
default:
break;
}
}
/**
* 刷新监听接口
*/
public interface RefreshListener {
public void onRefresh(PullDownScrollView view);
}
}
4.接口:
import android.view.View;
import android.view.animation.Animation;
/**
* @author xwangly@163.com
* @date 2013-7-10
* 下拉控件接口
*/
public interface IPullDownElastic {
public final static int RELEASE_To_REFRESH = 0;
public final static int PULL_To_REFRESH = 1;
public final static int REFRESHING = 2;
public final static int DONE = 3;
public View getElasticLayout();
public int getElasticHeight();
public void showArrow(int visibility);
public void startAnimation(Animation animation);
public void clearAnimation();
public void showProgressBar(int visibility);
public void setTips(String tips);
public void showLastUpdate(int visibility);
public void setLastUpdateText(String text);
/**
* 可以不用实现此方法,PullDownScrollView会处理ElasticLayout布局中的状态
* 如果需要特殊处理,可以实现此方法进行处理
*
* @param state @see RELEASE_To_REFRESH
* @param isBack 是否是松开回退
*/
public void changeElasticState(int state, boolean isBack);
}
5.默认实现:
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Animation;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
/**
* @author xwangly@163.com
* @date 2013-7-10
* 默认下拉控件布局实现
*/
public class PullDownElasticImp implements IPullDownElastic {
private View refreshView;
private ImageView arrowImageView;
private int headContentHeight;
private ProgressBar progressBar;
private TextView tipsTextview;
private TextView lastUpdatedTextView;
private Context mContext;
public PullDownElasticImp(Context context) {
mContext = context;
init();
}
private void init() {
// 刷新视图顶端的的view
refreshView = LayoutInflater.from(mContext).inflate(
R.layout.refresh_top_item, null);
// 指示器view
arrowImageView = (ImageView) refreshView
.findViewById(R.id.head_arrowImageView);
// 刷新bar
progressBar = (ProgressBar) refreshView
.findViewById(R.id.head_progressBar);
// 下拉显示text
tipsTextview = (TextView) refreshView.findViewById(R.id.refresh_hint);
// 下来显示时间
lastUpdatedTextView = (TextView) refreshView
.findViewById(R.id.refresh_time);
headContentHeight = Utils.dip2px(mContext, 50);
}
/**
* @return
*
*/
@Override
public View getElasticLayout() {
return refreshView;
}
/**
* @return
*
*/
@Override
public int getElasticHeight() {
return headContentHeight;
}
/**
* @param show
*
*/
@Override
public void showArrow(int visibility) {
arrowImageView.setVisibility(visibility);
}
/**
* @param animation
*
*/
@Override
public void startAnimation(Animation animation) {
arrowImageView.startAnimation(animation);
}
/**
*
*
*/
@Override
public void clearAnimation() {
arrowImageView.clearAnimation();
}
/**
* @param show
*
*/
@Override
public void showProgressBar(int visibility) {
progressBar.setVisibility(visibility);
}
/**
* @param tips
*
*/
@Override
public void setTips(String tips) {
tipsTextview.setText(tips);
}
/**
* @param show
*
*/
@Override
public void showLastUpdate(int visibility) {
lastUpdatedTextView.setVisibility(visibility);
}
/**
* @param text
*
*/
public void setLastUpdateText(String text) {
lastUpdatedTextView.setText(text);
}
/**
* @param state
* @param isBack
*
*/
@Override
public void changeElasticState(int state, boolean isBack) {
// TODO Auto-generated method stub
}
}
6.默认实现的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="-50.0dip"
android:orientation="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0.0dip"
android:layout_weight="1.0"
android:gravity="center"
android:orientation="horizontal" >
<!-- 箭头图像、进度条 -->
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="30dip" >
<!-- 箭头 -->
<ImageView
android:id="@+id/head_arrowImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/goicon" />
<!-- 进度条 -->
<ProgressBar
android:id="@+id/head_progressBar"
style="@android:style/Widget.ProgressBar.Small.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >
<!-- 提示 -->
<TextView
android:id="@+id/refresh_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉刷新"
android:textColor="#f2f2f2"
android:textSize="16sp" />
<!-- 最近更新 -->
<TextView
android:id="@+id/refresh_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="上次更新"
android:textColor="#b89766"
android:textSize="10sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
6.图片资源:
@drawable/goicon
完结
修改:从原工程中独立出来,并附上简单的工程,见附件。
- 大小: 406 Bytes
- 大小: 24.4 KB
- 大小: 25.2 KB
- 大小: 23.1 KB
分享到:
相关推荐
在ScrollView中实现下拉刷新相对复杂,因为ScrollView没有内置的刷新支持。开发者需要自定义监听器,检测滑动手势并触发相应的刷新动作。这通常涉及到对滚动事件的监听和处理,以及自定义头部视图来展示刷新状态。 ...
这个“Android下拉刷新控件(ListView好ScrollView版)”压缩包文件包含的是关于如何在Android中实现这种功能的具体资料,主要集中在ListView和ScrollView这两种常见的滚动视图上。 1. **下拉刷新概念** 下拉刷新...
在Android开发中,上拉加载和下拉刷新是常见的组件功能,用于提升用户体验,使得用户在滚动列表到顶部时能够方便地获取更多数据,而在滚动到底部时加载更多内容。本示例“Android自定义上拉加载下拉刷新控件”提供了...
在Android开发中,ScrollView是一个非常常见的...通过这些步骤,我们可以为用户提供一个直观、流畅的下拉刷新体验,增强应用的交互性和实用性。在实际开发中,我们应不断优化和完善这一功能,以适应不同场景的需求。
3. **ScrollView的下拉刷新**:ScrollView不支持内置的下拉刷新,因此需要更多的自定义工作。我们可以在ScrollView内部添加一个可滚动的容器,如LinearLayout,然后模拟ListView的头部刷新逻辑。监听ScrollView的...
本资源为Android项目中的下拉刷新控件,支持ListView和ScrollView两种视图。该控件具有高度可定制性,可根据您的需求进行二次开发。 该下拉刷新控件采用流畅的动画效果,提供轻松的用户体验。当用户下拉刷新时,...
本教程将介绍如何通过继承ScrollView来实现一个简单的下拉刷新功能,适用于初学者理解基本原理和动手实践。 首先,我们来看"PullToRefresh"这个文件,它是实现下拉刷新的核心部分。在Android中,下拉刷新通常涉及到...
在ScrollView中实现下拉刷新相对复杂,因为ScrollView本身并不支持直接的刷新事件。开发者需要自定义ScrollView或者使用第三方库,如PullToRefreshLayout,来实现类似SwipeRefreshLayout的效果。 4. **自定义控件**...
- 自定义View:由于ScrollView本身不支持直接添加下拉刷新功能,开发者通常需要自定义一个继承自ScrollView的类,添加额外的触摸事件处理,以检测用户的下拉动作。 - 使用第三方库:像PullToRefreshLayout这样的第...
自定义下拉刷新控件可以让你根据项目需求定制刷新动画和视觉效果,提升用户体验。下面我们将深入探讨如何在iOS中实现自定义下拉刷新控件。 一、下拉刷新基础 1. UIRefreshControl:苹果提供的系统下拉刷新控件,...
在Android应用开发中,下拉刷新控件是一个非常常见的功能,它允许用户通过在列表视图(ListView)或滚动视图(ScrollView)顶部向下拉动来更新数据。这种交互设计极大地提升了用户体验,使得用户能轻松获取最新的...
ScrollView是一个可以包含单个直接子视图的滚动容器,而ListView则是一个可以展示多个相同类型的条目并且支持滚动的控件。当在一个ScrollView中嵌套一个ListView时,可能会遇到一些特定的问题,尤其是在实现下拉刷新...