快速实现 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

时间: 2024-10-05 02:49:23

快速实现 ListView下拉,图片放大刷新操作的相关文章

iOS tableView下拉图片放大

事实上这个效果,本质上就是在你tableView下拉 造成offset时候. 保持你顶部图片的y坐标点还停留在下拉时屏幕的顶点(offset), 而图片的长度变为原始的height长度-(offset ) 就达到了下拉放大的效果. 直接上代码了: 1. 首先创建一个UIView作为headerView _topView = [[UIViewalloc]initWithFrame:CGRectMake(0,0,MCAPPWidth,80)]; _tableView.tableHeaderView

tableView下拉图片放大

其实这个效果,本质上就是在你tableView下拉 造成offset时候, 保持你顶部图片的y坐标点还停留在下拉时屏幕的顶点(offset), 而图片的长度变为原始的height长度-(offset ) 就达到了下拉放大的效果. 直接上代码了: 1. 首先创建一个UIView作为headerView _topView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, MCAPPWidth, 80)]; _tableView.tableHeaderVi

第六十七篇、OC_UITableView head下拉图片放大的效果

(一) 布置UITableview 我们首先要通过设置UITableview的内容偏移 self.tableView.contentInset 来为图片视图留出位置,这里我们的图片高度暂定为280 const CGFloat contentInset = 280; @interface ViewController ()<UITableViewDelegate,UITableViewDataSource> @property (nonatomic, strong) UITableView *t

iOS下拉图片放大

效果图 开始简单的代码过程 其实思路很简单 就是 让tableView偏移 一图片的高度,然后在把图片添加到tableView中,然后再监听didScrollView,在里面改变图片的frame - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.automaticallyAdjustsScrollViewI

iOS----实现scrollView或者scrollView的子类下拉图片放大的效果

代码是通过Tableview来说明的,用在其他情况下同样适用 - (void)viewDidLoad { [super viewDidLoad]; _imageview = [[UIImageView alloc]init]; _imageview.image = [UIImage imageNamed:@"F2.jpg"]; self.imageview.frame =CGRectMake(0, -150, self.tableView.frame.size.width, 150);

ScrollView下拉视图放大

在网上找了好多下拉Scrollview图片放大的效果,结果看了一下都不太靠谱,后来自己想了一个办法解决了这个问题.跟大家分享一下. 其实就是对Scrollview下拉的监听将视图放大.不管是View还是ImageView放大就ok了.主要代码如下: /** 手指放下的监听*/ float initTouchY=0; mScrollView.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, Motio

ListView下拉刷新

public class MyListView extends ListView implements OnScrollListener { private static final int STATE_NORMAL = 0;  // 正常状态 private static final int STATE_PULL = 1;    // 下来状态 private static final int STATE_RELEASE = 2; // 释放状态 private static final in

Android—自定义控件实现ListView下拉刷新

这篇博客为大家介绍一个android常见的功能——ListView下拉刷新(参考自他人博客,网址忘记了,阅读他的代码自己理解注释的,希望能帮助到大家): 首先下拉未松手时候手机显示这样的界面: 下面的代码是自定的扎样的控件: package com.dhsr.smartID.view; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import andr

ListView下拉刷新、上拉载入更多之封装改进

在Android中ListView下拉刷新.上拉载入更多示例一文中,Maxwin兄给出的控件比较强大,前面有详细介绍,但是有个不足就是,里面使用了一些资源文件,包括图片,String,layout,这样不利于封装打包,下面我将源码进行改进,所有布局全部用代码实现,这样直接将src和assets打包成jar就成了一个非常方便以后使用的扩展ListView控件,代码如下: XListView: /** * @file XListView.java * @package me.maxwin.view