Android安卓下拉阻尼效果实现原理及简单实例

原理
  这种效果是通过自定义控件的方式来实现的,我自定义了一个控件类型,这个自定义控件(PullDownDumperLayout)继承自线性布局(LinearLayout)。
  用户可以下拉弹出的那个视图,例如微信的小程序列表,开发者只是将这个视图移出了父元素之外,所以不可见,我们暂且称之为隐藏头部,只有下拉到一定程度才会弹出,而主体,例如微信的联系人列表,则是可见的,布局见下图。

实现这个效果需要我们做三件工作:

隐藏作为头部的控件
监听用户对屏幕的操作事件
实现下拉回弹的动画效果
  我们这个自定义控件会自动获取内部第一个子元素充当头部,其余的元素则是充当可见的主体(详见代码中的注释)。
  基本的布局原理差不多就这样了,但是我们还需要让自定义控件监听用户的手势操作,例如上下滑动等。这里我和灵感来源的那篇博客一样,让自定义控件实现View.OnTouchListener接口,实现内部的onTouch方法可以监听来自屏幕的所有触摸操作。代码中我让头部和第二个子元素(可见的主体)注册了这个监听器,这是为了方便读者理解,读者可根据自己的需求进行修改。
  注意,对于不能监听屏幕触摸事件的控件需要添加:
    android:clickable="true"
  至此,我们已经可以进行布局和监听用户手势了,但是还需要实现一个头部展开和隐藏的动画效果。当用户将隐藏头部下拉或上滑到一定高度时,这个效果就会被触发,这需要依赖上面所述的onTouch方法。动画效果的实现需要另开一个线程进行操作,线程的启动方式我们可以采用继承AsyncTask类来实现。
  除此之外,我们可能会多次复用这个控件,所以在自定义控件类的最后还需要一些调整参数的set方法。
  这里提个醒,在接下来的代码中,我们的自定义控件因为继承自LinearLayout,里面需要重写onLayout方法,而onLayout方法顾名思义就是布局,这个方法在Activity中的onCreate方法执行之后才会被调用,所以我们可以在Activity的onCreate方法中利用findViewById获取实例,调用上面提到的set方法进行参数的初始化。
  LinearLayout中不止onLayout一个方法,详细解析请读者移步其他关于XML标签加载过程的文章,这里不做赘述。

代码
PullDownDumperLayout .java:

public class PullDownDumperLayout extends LinearLayout implements View.OnTouchListener {

/**
* 取布局中的第一个子元素为下拉隐藏头部
*/
private View mHeadLayout;

/**
* 隐藏头部布局的高的负值
*/
private int mHeadLayoutHeight;

/**
* 隐藏头部的布局参数
*/
private MarginLayoutParams mHeadLayoutParams;

/**
* 判断是否为第一次初始化,第一次初始化需要把headView移出界面外
*/
private boolean mOnLayoutIsInit=false;

/**
* 移动时,前一个坐标
*/
private float mMoveY;

/**
* 如果为false,会退出头部展开或隐藏动画
*/
private boolean mChangeHeadLayoutTopMargin;

/**
* 触发动画的分界线,由mRatio计算得到
*/
private int mBoundary;

/**
* 头部布局的隐藏和展开速度,以及单次执行时间
*/
private int mHeadLayoutHideSpeed;
private int mHeadLayoutUnfoldSpeed;
private long mSleepTime;

/**
* 触发动画的分界线,头部布局上半部分和整体高度的比例
*/
private double mRatio;

public PullDownDumperLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//初始化参数,根据自己的需求调整
mHeadLayoutHideSpeed=-20;
mHeadLayoutUnfoldSpeed=20;
mSleepTime=10;
mRatio=0.5;
}

/**
* 布局开始设置每一个控件
* 在activity的onCreate执行之后才会执行
* 因此可以在onCreate中调用set方法设置参数
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(!mOnLayoutIsInit && changed) {
//将第一个子元素作为头部移出界面外
mHeadLayout = this.getChildAt(0);
mHeadLayoutHeight=-mHeadLayout.getHeight();
mBoundary=(int)(mRatio*mHeadLayoutHeight);//计算触发动画分界线
mHeadLayoutParams=(MarginLayoutParams) mHeadLayout.getLayoutParams();
mHeadLayoutParams.topMargin=mHeadLayoutHeight;
mHeadLayout.setLayoutParams(mHeadLayoutParams);
//TODO 设置手势监听器,不能触碰的控件需要添加android:clickable="true"
getChildAt(1).setOnTouchListener(this);
mHeadLayout.setOnTouchListener(this);
//标记已被初始化
mOnLayoutIsInit=true;
}
}

/**
* 屏幕触摸操作监听器
* @return false则注册本监听器的控件将不会对事件做出响应,true则相反
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mMoveY=event.getRawY();//捕获按下时的坐标,初始化mMoveY
mChangeHeadLayoutTopMargin=false;
break;
case MotionEvent.ACTION_MOVE:
float currY=event.getRawY();
int vector=(int)(currY-mMoveY);//向量,用于判断手势的上滑和下滑
mMoveY=currY;
//判断是否为滑动
if(Math.abs(vector)==0){
return false;
}
//头部完全隐藏时不再向上滑动
if (vector < 0 && mHeadLayoutParams.topMargin <= mHeadLayoutHeight) {
return false;
}
//头部完全展开时不再向下滑动
if (vector > 0 && mHeadLayoutParams.topMargin >= 0) {
return false;
}

//对增量进行修正,对滑动距离进行减半
int topMargin = mHeadLayoutParams.topMargin + (vector/2);//阻尼值
if(topMargin>0){
// 瞬间拉动的距离超过了头部高度,因为这一瞬间很短,这里采用直接赋值的方式
// 如需平滑过渡,要另开线程,并且监听到ACTION_DOWN时线程可被打断
topMargin = 0;
}
else if(topMargin<mHeadLayoutHeight){
// 瞬间拉动的距离超过了头部高度,因为这一瞬间很短,这里采用直接赋值的方式
// 如需平滑过渡,要另开线程,并且监听ACTION_DOWN时线程可被打断
topMargin = mHeadLayoutHeight;
}
//用户对屏幕的滑动将会改变控件的TopMargin
mHeadLayoutParams.topMargin = topMargin ;
mHeadLayout.setLayoutParams(mHeadLayoutParams);
break;
default:
//TODO 出现其他触碰事件,如MotionEvent.ACTION_UP时,根据阈值判断此时头部应该弹出还是隐藏
mChangeHeadLayoutTopMargin=true;
if(mHeadLayoutParams.topMargin<=mBoundary){
//隐藏
new MoveHeaderTask().execute(true);
}
else{
//展开
new MoveHeaderTask().execute(false);
}
break;
}
return false;
}

/**
* 新线程,隐藏或者展开头部布局,线程可被ACTION_DOWN打断
*/
class MoveHeaderTask extends AsyncTask<Boolean, Integer, Integer> {

/**
*
* @param opt true为隐藏动画,false为展开动画
* @return
*/
@Override
protected Integer doInBackground(Boolean... opt) {
int topMargin=mHeadLayoutParams.topMargin;
//true为隐藏,false为展开
int speed=(opt[0])?mHeadLayoutHideSpeed:mHeadLayoutUnfoldSpeed;
while(mChangeHeadLayoutTopMargin){
topMargin += speed;
if (topMargin <= mHeadLayoutHeight||topMargin>=0) {
topMargin=(opt[0])?mHeadLayoutHeight:0;
publishProgress(topMargin);
break;
}
publishProgress(topMargin);
sleep(mSleepTime);
}
return null;
}

//调用publishProgress后会执行
@Override
protected void onProgressUpdate(Integer... topMargin) {
mHeadLayoutParams.topMargin=topMargin[0];
mHeadLayout.setLayoutParams(mHeadLayoutParams);
}

}

//调整参数
public void setHeadLayoutHideSpeed(int speed){
this.mHeadLayoutHideSpeed=speed;
}
public void setHeadLayoutUnfoldSpeed(int speed){
this.mHeadLayoutUnfoldSpeed=speed;
}
public void setSleepTime(long time){
this.mSleepTime=time;
}
public void setRatio(double ratio){
this.mRatio=ratio;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<com.example.pulldowndumpertest.PullDownDumperLayout
android:tag="记得将这个标签修改为自己的包名"
android:id="@+id/PullDownDumper"
android:layout_width="900px"
android:layout_height="1920px"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="@null"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="500px"
android:orientation="vertical"
android:background="@color/colorPrimary"
android:clickable="true">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="隐藏头部"
android:textSize="100px"
android:gravity="center"
android:textColor="#FFFFFF"
android:background="@null"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1700px"
android:background="@color/colorPrimaryDark"
android:clickable="true">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="可见主体"
android:textSize="100px"
android:gravity="center"
android:textColor="#FFFFFF"
android:background="@null"/>
</LinearLayout>
</com.example.pulldowndumpertest.PullDownDumperLayout>

</android.support.constraint.ConstraintLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
MainActivity.java:

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//TODO 读者可在这里初始化参数
PullDownDumperLayout pddl=findViewById(R.id.PullDownDumper);

}
}
1
2
3
4
5
6
7
8
9
10
11
下面是笔者正在使用的自定义控件,比上述的控件多了一个效果:
  头部处于隐藏或展开的不同状态时,触发动画效果的分界线可以随状态不同而改变。
  还是拿最新版的微信小程序入口来讲,用户在下拉时,小程序界面会占用整个屏幕,如果触发动画的分界线太低,这样导致的结果是用户可能无法通过上滑重新返回联系人列表,但由于微信没有对滑动距离进行减半处理,所以不存在上述问题,可能是出于防止误触的原因,从小程序界面返回联系人列表的方式改用点击底部的一个按钮。而我的控件可以通过改变触发动画效果的分界线来解决这一问题,感兴趣的读者可以研究一下。

public class PullDownDumperLayout extends LinearLayout implements View.OnTouchListener {

/**
* 取布局中的第一个子元素为下拉隐藏头部
*/
private View mHeadLayout;

/**
* 隐藏头部布局的高的负值
*/
private int mHeadLayoutHeight;

/**
* 隐藏头部的布局参数
*/
private MarginLayoutParams mHeadLayoutParams;

/**
* 判断是否为第一次初始化,第一次初始化需要把headView移出界面外
*/
private boolean mOnLayoutIsInit=false;

/**
* 从配置获取的滚动判断阈值,为两点间的距离,超过此阈值判断为滚动
*/
// private int mScaledTouchSlop;

/**
* 按下时的y轴坐标
*/
// private float mDownY;

/**
* 移动时,前一个坐标
*/
private float mMoveY;

/**
* 如果为false,会退出头部展开或隐藏动画
*/
private boolean mChangeHeadLayoutTopMargin;

/**
* 头部布局的隐藏和展开速度,以及单次执行时间
*/
private int mHeadLayoutHideSpeed;
private int mHeadLayoutUnfoldSpeed;
private long mSleepTime;

/**
* 初始化头部布局的偏移值,数值越大,头部可见部分越多,预设值为0,即初始时头部完全不可见
*/
private int mTopMarginOffset;

/**
* 触发动画的分界线,头部布局上半部分和整体高度的比例
*/
private double mUnfoldRatio;
private double mHideRatio;

/**
* 触发动画的分界线,初始值由mRatio计算得到
* 头部处于隐藏时等于mUnfoldBoundary
* 头部处于展开时等于mHideBoundary
* mBoundary在onTouch的ACTION_DOWN中变化
*/
private int mBoundary;
private int mUnfoldBoundary;
private int mHideBoundary;

/**
* 阻尼值,越大越难拖动,呈线性趋势
*/
private int mDumper;

public PullDownDumperLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// mScaledTouchSlop= ViewConfiguration.get(context).getScaledTouchSlop();
mHeadLayoutHideSpeed=-30;
mHeadLayoutUnfoldSpeed=30;
mSleepTime=10;
mUnfoldRatio=0.6;
mHideRatio=mUnfoldRatio;
mDumper=2;
mTopMarginOffset=-200;
}

/**
* 布局开始设置每一个控件
* 在activity的onCreate执行之后才会执行
* 因此可以在onCreate中调用set方法设置参数
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//只初始化一次
if(!mOnLayoutIsInit && changed) {
//将第一个子元素作为头部移出界面外
mHeadLayout = this.getChildAt(0);
mHeadLayoutHeight=-mHeadLayout.getHeight();
mUnfoldBoundary=(int)(mUnfoldRatio*mHeadLayoutHeight);//计算触发展开动画分界线
mHideBoundary=(int)(mHideRatio*mHeadLayoutHeight);//计算触发隐藏动画分界线
mBoundary=mUnfoldBoundary;//触发动画的分界线初始为mUnfoldBoundary
mHeadLayoutHeight-=mTopMarginOffset;//头部隐藏布局可见的部分
mHeadLayoutParams=(MarginLayoutParams) mHeadLayout.getLayoutParams();
mHeadLayoutParams.topMargin=mHeadLayoutHeight;
mHeadLayout.setLayoutParams(mHeadLayoutParams);
//TODO 设置手势监听器,不能触碰的控件需要添加android:clickable="true"
getChildAt(1).setOnTouchListener(this);
mHeadLayout.setOnTouchListener(this);
//标记已被初始化
mOnLayoutIsInit=true;
}
}

/**
* 屏幕触摸操作监听器
* @return false: 注册本监听器的控件将不会对事件做出响应,true则相反
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//根据此时处于完全展开或完全隐藏决定mBoundary的值,如果两种情况都不满足则不做改变
if(mHeadLayoutParams.topMargin==mHeadLayoutHeight)
mBoundary=mUnfoldBoundary;
else if(mHeadLayoutParams.topMargin==0)
mBoundary=mHideBoundary;

// mDownY=event.getRawY();//获取按下的屏幕y坐标
mMoveY=event.getRawY();
mChangeHeadLayoutTopMargin=false;//false会打断隐藏或展开头部布局的动画
break;
case MotionEvent.ACTION_MOVE:
float currY=event.getRawY();
int vector=(int)(currY-mMoveY);//向量,用于判断手势的上滑和下滑
mMoveY=currY;
//判断是否为滑动
if(Math.abs(vector)==0){
return false;
}
//头部完全隐藏时不再向上滑动
if (vector < 0 && mHeadLayoutParams.topMargin <= mHeadLayoutHeight) {
return false;
}
//头部完全展开时不再向下滑动
else if (vector > 0 && mHeadLayoutParams.topMargin >= 0) {
return false;
}

//对增量进行修正
int topMargin = mHeadLayoutParams.topMargin + (vector/mDumper);
if(topMargin>0){
// 瞬间拉动的距离超过了头部高度,因为这一瞬间很短,这里采用直接赋值的方式
// 如需实现平滑过渡,要另开线程,并且监听到ACTION_DOWN时线程可被打断
topMargin = 0;
}
else if(topMargin<mHeadLayoutHeight){
// 瞬间拉动的距离超过了头部高度,因为这一瞬间很短,这里采用直接赋值的方式
// 如需实现平滑过渡,要另开线程,并且监听ACTION_DOWN时线程可被打断
topMargin = mHeadLayoutHeight;
}

//使参数生效
mHeadLayoutParams.topMargin = topMargin ;
mHeadLayout.setLayoutParams(mHeadLayoutParams);
break;
default:
//出现其他触碰事件,如MotionEvent.ACTION_UP时,根据阈值mBoundary判断此时头部应该弹出还是隐藏
mChangeHeadLayoutTopMargin=true;//允许执行动画
if(mHeadLayoutParams.topMargin<=mBoundary){
//隐藏
new MoveHeaderTask().execute(true);
}
else{
//展开
new MoveHeaderTask().execute(false);
}
break;
}
return false;
}

/**
* 新线程,隐藏或者展开头部布局,线程可被ACTION_DOWN打断
*/
private class MoveHeaderTask extends AsyncTask<Boolean, Integer, Integer> {

/**
*
* @param opt true为隐藏动画,false为展开动画
* @return
*/
@Override
protected Integer doInBackground(Boolean... opt) {
int topMargin=mHeadLayoutParams.topMargin;
//true为隐藏,false为展开
int speed=(opt[0])?mHeadLayoutHideSpeed:mHeadLayoutUnfoldSpeed;
while(mChangeHeadLayoutTopMargin){
topMargin += speed;
if (topMargin <= mHeadLayoutHeight||topMargin>=0) {
topMargin=(opt[0])?mHeadLayoutHeight:0;
publishProgress(topMargin);
break;
}
publishProgress(topMargin);
sleep(mSleepTime);
}
return null;
}

//调用publishProgress后会执行
@Override
protected void onProgressUpdate(Integer... topMargin) {
mHeadLayoutParams.topMargin=topMargin[0];
mHeadLayout.setLayoutParams(mHeadLayoutParams);
}

}

//调整参数
public void setHeadLayoutHideSpeed(int speed){
this.mHeadLayoutHideSpeed=speed;
}
public void setHeadLayoutUnfoldSpeed(int speed){
this.mHeadLayoutUnfoldSpeed=speed;
}
public void setSleepTime(long time){
this.mSleepTime=time;
}
public void setDumper(int dumper){
this.mDumper=dumper;
}
public void setTopMarginOffset(int offset){
this.mTopMarginOffset=-offset;
}

/**
* 头部处于隐藏状态时,触发展开动画的分界线
* @param ratio 头部布局上部分与下部分的分界线
*/
public void setUnfoldRatio(double ratio){
this.mUnfoldRatio=ratio;
}

/**
* 头部处于展开状态时,触发隐藏动画的分界线
* @param ratio 头部布局上部分与下部分的分界线
*/
public void setHideRatio(double ratio){
this.mHideRatio=ratio;
}
}```

---------------------

原文地址:https://www.cnblogs.com/hyhy904/p/11069797.html

时间: 2024-10-08 22:47:50

Android安卓下拉阻尼效果实现原理及简单实例的相关文章

Android PullToRefresh下拉刷新控件的简单使用

PullToRefresh这个开源库早就听说了,不过一直没用过.作为一个经典的的开源库,我觉得还是有必要认识一下. 打开github上的网址:https://github.com/chrisbanes/Android-PullToRefresh 网页一打开就看到一个大大的提醒(说是该项目已经不再维护了): 不管怎样先下载下来再说: 下载解压后,打开文件夹如下图所示: 然后导入到工程,如下图所示:(其中的PullToRefreshListFragment和PullToRefreshViewPage

Android ListView下拉/上拉刷新:设计原理与实现

 <Android ListView下拉/上拉刷新:设计原理与实现> Android上ListView的第三方开源的下拉刷新框架很多,应用场景很多很普遍,几乎成为现在APP的通用设计典范,甚至谷歌官方都索性在Android SDK层面支持下拉刷新,我之前写了一篇文章<Android SwipeRefreshLayout:谷歌官方SDK包中的下拉刷新>专门介绍过(链接地址:http://blog.csdn.net/zhangphil/article/details/4696537

Android仿苹果版QQ下拉刷新实现(一) ——打造简单平滑的通用下拉刷新控件

前言: 因为公司人员变动原因,导致了博主四个月没有动安卓,一直在做IOS开发,如今接近年前,终于可以花一定的时间放在安卓上了.好了,废话不多说,今天我们要带来的效果是苹果版本的QQ下拉刷新.首先看一下目标效果以及demo效果:      因为此效果实现的步骤较多,所以今天博主要实现以上效果的第一步——打造一个通用的下拉刷新控件,具体效果如下: GIF图片比较大,还希望读者能耐心等待一下下从效果图中可以看出,我们的下拉刷新的滑动还是很流畅的,可能大多数开发者用的是XListview或者PullTo

Xamarin. Android实现下拉刷新功能

下拉刷新功能在安卓和iOS中非常常见,一般实现这样的功能都是直接使用第三方的库,网上能找到很多这样的开源库.然而在Xamarin. Android中要实现一个好用的下拉刷新功能却不是很容易,在网上找了几个Xamarin.Android的下拉刷新控件,都不是很满意,所以想重新绑定一个java写的下拉刷新控件.在网上找了几个这样的开源库,通过对比发现android-pull-to-refresh实现的功能比较多,实现的效果也比较满意. Android-Pull-To-Refresh项目地址:http

Android实现下拉导航选择菜单效果【转载地址:http://www.cnblogs.com/hanyonglu/archive/2012/07/31/2617488.html】

本文介绍在Android中如何实现下拉导航选择菜单效果.   关于下拉导航选择菜单效果在新闻客户端中用的比较多,当然也可以用在其他的项目中,这样可以很方便的选择更多的菜单.我们可以让我们的应用顶部有左右滑动或进行切换的导航菜单,也可以为了增强用户体验在应用中添加这样的下拉导航选择菜单效果. 关于它的实现原理,其实也是挺简单的,就是使用PopupWindow来进行展现,在显示时控制其高度并配置以相应的动画效果.在PopupWindow中我使用GridView来控制里面的菜单项,每个菜单项对应相应的

Android实现下拉导航选择菜单效果(转)

本文转载自互联网 关于下拉导航选择菜单效果在新闻客户端中用的比较多,当然也可以用在其他的项目中,这样可以很方便的选择更多的菜单.我们可以让我们的应用顶部有左右滑动或进行切换的导航菜单,也可以为了增强用户体验在应用中添加这样的下拉导航选择菜单效果. 关于它的实现原理,其实也是挺简单的,就是使用PopupWindow来进行展现,在显示时控制其高度并配置以相应的动画效果.在PopupWindow中我使用GridView来控制里面的菜单项,每个菜单项对应相应的图片和文字.当然了,也有其他的实现方式.为了

自个儿写Android的下拉刷新/上拉加载控件 (续)

本文算是对之前的一篇博文<自个儿写Android的下拉刷新/上拉加载控件>的续章,如果有兴趣了解更多的朋友可以先看一看之前的这篇博客. 事实上之所以会有之前的那篇博文的出现,是起因于前段时间自己在写一个练手的App时很快就遇到这种需求.其实我们可以发现类似这样下拉刷新.上拉加载的功能正在变得越来越普遍,可以说如今基本上绝大多数的应用里面都会使用到.当然,随着Android的发展,已经有不少现成的可以实现这种需求的"轮子"供我们使用了. 但转过头想一下想,既然本来就是自己练手

Android自定义控件--下拉刷新的实现

我们在使用ListView的时候,很多情况下需要用到下拉刷新的功能.为了了解下拉刷新的底层实现原理,我采用自定义ListView控件的方式来实现效果. 实现的基本原理是:自定义ListView,给ListView加载头布局,然后动态的控制头布局的现实与隐藏.ListView初始化的时候,头布局是隐藏的,当手指往下拉的时候,根据手指移动的距离与头布局的高度的关系来控制头布局的显示.具体的控制思路详见后边的代码,代码中的注释很详细.先来看一下效果图:                     头布局的

android学习---下拉刷新组建

Google官方的下拉刷新组建 activity代码实现: /** * The SwipeRefreshLayout should be used whenever the user * can refresh the contents of a view via a vertical swipe gesture. * */public class MainActivity extends Activity implements SwipeRefreshLayout.OnRefreshListe