快速实现 ListView下拉,图片放大刷新操作

今天要写的这个效果属于刷新类,比较实用,像很多流行的 app 都是用了这种效果,大家熟知的QQ空间、微博个人主页等,这个效果在 github 上也有别人实现好的源码,点击查看。这里也参考了上面的源码;还是那句话,看 blog主要是学习其中的原理和思路。

动态效果图


图片放大的原理是什么呢?

 

通过改变图片显示控件 ImageView 的父控件的高度,比如这里的头部 View 是一个 FrameLayout,FrameLayout 中再 通过 add 方法把图片 View 添加进去,addView(ImageView),ImageView有几个属性是要特别注意的,ImageView 的放缩类型为从中间截取

1
setScaleType(ImageView.ScaleType.CENTER_CROP);

并且宽高设为匹配父控件;所以想要图片有放大效果,只需设置 FrameLayout 的 LayoutParams 中的 height值,通过改变 height 的值从而改变 ImageView 的显示高度。讲的有点混乱,可以结合下面的代码来理解。

如果你是对手势事件处理很了解的朋友,对这个效果的实现应该没有什么难度,唯一的一点就是判断何时该放大图片,何时该滚动ListView。

这里就直接贴代码:

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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
/**
* Created by gyzhong on 15/3/22.
*/
public class PullZoomListView extends ListView {
/*头部View 的容器*/
private FrameLayout mHeaderContainer;
/*头部View 的图片*/
private ImageView mHeaderImg;
/*屏幕的高度*/
private int mScreenHeight;
/*屏幕的宽度*/
private int mScreenWidth;
 
private int mHeaderHeight;
 
/*无效的点*/
private static final int INVALID_POINTER = -1;
/*滑动动画执行的时间*/
private static final int MIN_SETTLE_DURATION = 200; // ms
/*定义了一个时间插值器,根据ViewPage控件来定义的*/
private static final Interpolator sInterpolator = new Interpolator() {
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
 
/*记录上一次手指触摸的点*/
private float mLastMotionX;
private float mLastMotionY;
 
/*当前活动的点Id,有效的点的Id*/
protected int mActivePointerId = INVALID_POINTER;
/*开始滑动的标志距离*/
private int mTouchSlop;
 
/*放大的倍数*/
private float mScale;
/*上一次放大的倍数*/
private float mLastScale;
 
/*最大放大的倍数*/
private final float mMaxScale = 2.0f;
/*是否需要禁止ListView 的事件响应*/
private boolean isNeedCancelParent;
 
/*这个不解释*/
private OnScrollListener mScrollListener ;
 
/*下拉刷新的阈值*/
private final float REFRESH_SCALE = 1.20F;
 
/*下拉刷新监听*/
private OnRefreshListener mRefreshListener ;
 
public PullZoomListView(Context context) {
super(context);
init(context);
}
 
public PullZoomListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
 
public PullZoomListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
 
private void init(Context context) {
 
/*这里获取的是一个无用值,可忽略*/
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
 
/*创建头部View 的容器*/
mHeaderContainer = new FrameLayout(context);
/*获取屏幕的像素值*/
DisplayMetrics metrics = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metrics);
mScreenHeight = metrics.heightPixels;
mScreenWidth = metrics.widthPixels;
/*设置头部View 的初始大小*/
mHeaderHeight = (int) ((9 * 1.0f / 16) * mScreenWidth);
LayoutParams absLayoutParams = new LayoutParams(mScreenWidth, mHeaderHeight);
mHeaderContainer.setLayoutParams(absLayoutParams);
/*创建图片显示的View*/
mHeaderImg = new ImageView(context);
FrameLayout.LayoutParams imgLayoutParams = new FrameLayout.LayoutParams
(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mHeaderImg.setScaleType(ImageView.ScaleType.CENTER_CROP);
mHeaderImg.setLayoutParams(imgLayoutParams);
mHeaderContainer.addView(mHeaderImg);
 
/*增加头部View*/
addHeaderView(mHeaderContainer);
/*设置监听事件*/
super.setOnScrollListener(new InternalScrollerListener() );
 
}
/*处理事件用*/
 
@Override
public boolean onTouchEvent(MotionEvent ev) {
 
final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
 
switch (action) {
case MotionEvent.ACTION_DOWN:
 
/*计算 x,y 的距离*/
int index = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
if (mActivePointerId == INVALID_POINTER)
break;
mLastMotionX = MotionEventCompat.getX(ev, index);
mLastMotionY = MotionEventCompat.getY(ev, index);
// 结束动画,目前没做处理,可忽略
abortAnimation();
/*计算算一次放缩的比例*/
mLastScale = (this.mHeaderContainer.getBottom() / this.mHeaderHeight);
/*当按下的时候把这个标志为设为有效*/
isNeedCancelParent = true ;
break;
case MotionEvent.ACTION_MOVE:
int indexMove = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, indexMove);
 
if (mActivePointerId == INVALID_POINTER) {
/*这里相当于松手*/
finishPull();
isNeedCancelParent = true ;
} else {
/*这是一个关键值,通过这个值来判断是否需要放大图片*/
if (mHeaderContainer.getBottom() >= mHeaderHeight) {
ViewGroup.LayoutParams params = this.mHeaderContainer.getLayoutParams();
final float y = MotionEventCompat.getY(ev, indexMove);
float dy = y - mLastMotionY;
float f = ((y - this.mLastMotionY + this.mHeaderContainer
.getBottom()) / this.mHeaderHeight - this.mLastScale)
/ 2.0F + this.mLastScale;
if ((this.mLastScale <= 1.0D) && (f <= this.mLastScale)) {
params.height = this.mHeaderHeight;
this.mHeaderContainer.setLayoutParams(params);
return super.onTouchEvent(ev);
}
/*这里设置紧凑度*/
dy = dy * 0.5f * (mHeaderHeight * 1.0f / params.height);
mLastScale = (dy + params.height) * 1.0f / mHeaderHeight;
mScale = clamp(mLastScale, 1.0f, mMaxScale);
 
// Log.v(“zgy”, “=======mScale=====” + mLastScale+”,f = “+f);
params.height = (int) (mHeaderHeight * mScale);
mHeaderContainer.setLayoutParams(params);
mLastMotionY = y;
/*这里,如果图片有放大,则屏蔽ListView 的其他事件响应*/
if(isNeedCancelParent ){
isNeedCancelParent = false;
MotionEvent motionEvent = MotionEvent.obtain(ev);
motionEvent.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(motionEvent);
}
return true;
}
mLastMotionY = MotionEventCompat.getY(ev, indexMove);
 
}
 
break;
case MotionEvent.ACTION_UP:
/*结束事件响应,做相应的操作*/
finishPull();
 
break;
case MotionEvent.ACTION_POINTER_UP:
/*这里需要注意,多点处理,这里的处理方式是:如果有两个手指按下,抬起的是后按下的手指,则不做处理
* 如果抬起的是最先按下的手指,则复原图片效果。
* */
int pointUpIndex = MotionEventCompat.getActionIndex(ev);
int pointId = MotionEventCompat.getPointerId(ev, pointUpIndex);
if (pointId == mActivePointerId) {
/*松手执行结束拖拽操作*/
/*结束事件响应,做相应的操作*/
finishPull();
}
 
break;
 
}
 
return super.onTouchEvent(ev);
}
 
@Override
public void setOnScrollListener(OnScrollListener l) {
mScrollListener = l ;
}
 
private void abortAnimation() {
/*啥都没做,暂时没做而已*/
}
 
private void finishPull() {
mActivePointerId = INVALID_POINTER;
/*这是一个阈值,如果成立,则表示图片已经放大了,在手指抬起的时候需要复原图片*/
if (mHeaderContainer.getBottom() > mHeaderHeight){
 
// Log.v(“zgy”, “===super====onTouchEvent========”);
/<em>这里是下拉刷新的阈值,当达到了,则表示需要刷新,可以添加刷新动画</em>/
if (mScale > REFRESH_SCALE){
if (mRefreshListener != null){
mRefreshListener.onRefresh();
}
}
//图片复原动画
pullBackAnimation();
}
}
/**
* 这是属性动画的知识,不懂的可以去看看属性动画的知识
*/
private void pullBackAnimation(){
ValueAnimator pullBack = ValueAnimator.ofFloat(mScale , 1.0f);
pullBack.setInterpolator(sInterpolator);
pullBack.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
LayoutParams params = (LayoutParams) mHeaderContainer.getLayoutParams();
params.height = (int) (mHeaderHeight * value);
mHeaderContainer.setLayoutParams(params);
}
});
pullBack.setDuration((long) (MIN_SETTLE_DURATION*mScale));
pullBack.start();
 
}
 
/**
* 通过事件和点的 id 来获取点的索引
*
* @param ev
* @param id
* @return
*/
private int getPointerIndex(MotionEvent ev, int id) {
int activePointerIndex = MotionEventCompat.findPointerIndex(ev, id);
if (activePointerIndex == -1)
mActivePointerId = INVALID_POINTER;
return activePointerIndex;
}
 
public void setOnRefreshListener(OnRefreshListener l){
mRefreshListener = l ;
}
 
public ImageView getHeaderImageView() {
return this.mHeaderImg;
}
 
private float clamp(float value, float min, float max) {
return Math.min(Math.max(value, min), max);
}
 
private class InternalScrollerListener implements OnScrollListener{
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
 
if (mScrollListener != null){
mScrollListener.onScrollStateChanged(view,scrollState);
}
}
 
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
float diff = mHeaderHeight - mHeaderContainer.getBottom();
if ((diff > 0.0F) && (diff < mHeaderHeight)) {
int i = (int) (0.3D * diff);
mHeaderImg.scrollTo(0, -i);
} else if (mHeaderImg.getScrollY() != 0) {
mHeaderImg.scrollTo(0, 0);
}
 
Log.v("zgy","=========height==="+getScrolledY());
 
if (mScrollListener != null){
mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
}
 
public int getScrolledY() {
View c = getChildAt(0);
if (c == null) {
return 0;
}
 
int firstVisiblePosition = getFirstVisiblePosition();
int top = c.getTop();
 
int headerHeight = 0;
if (firstVisiblePosition >= 1) {
headerHeight = mHeaderHeight;
}
 
return -top + firstVisiblePosition * c.getHeight() + headerHeight;
}
 
public interface OnRefreshListener {
void onRefresh() ;
}
 
public void computeRefresh(){
if (mActivePointerId != INVALID_POINTER){
 
}
}
 
}

比较难理解的地方都做了注释,所以。。。应该还是很好理解的。

总结:

 

今天 blog的一个难点就是,手势的处理,下拉放大的条件判断;针对这种问题,我也没有很好的解决方案,我的经验就是,直接通过 打印 Log 的方式来寻找规律,因为有的时候想的挺烦的,而且逻辑很容易混乱。


接着是图片放大的原理,知道了就很好实现此功能。

http://mp.weixin.qq.com/s?__biz=MzA4NDM2MjAwNw==&mid=206325282&idx=1&sn=1b16ca4ca2fdff147ee282c2b85ef61e&scene=1#rd

原文地址:https://www.cnblogs.com/shanzei/p/4651340.html