最新消息:欢迎访问Android开发中文站!商务联系微信:loading_in

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

开发进阶 loading 16785浏览 0评论

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

动态效果图
这里写图片描述

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

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


setScaleType(ImageView.ScaleType.CENTER_CROP);

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

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

这里就直接贴代码:


/**
 * 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 的方式来寻找规律,因为有的时候想的挺烦的,而且逻辑很容易混乱。
接着是图片放大的原理,知道了就很好实现此功能。

源码

转载请注明:Android开发中文站 » 快速实现 ListView下拉,图片放大刷新操作

您必须 登录 才能发表评论!