zoukankan      html  css  js  c++  java
  • 快速实现 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

  • 相关阅读:
    MyBatis入门
    Java JDBC
    Spring MVC
    Java内存模型
    Java日志
    Java I/O模型
    Java异常处理
    Java泛型设计
    Java反射
    Java代理
  • 原文地址:https://www.cnblogs.com/shanzei/p/4651340.html
Copyright © 2011-2022 走看看