自己实现一个PullToZoomListView

上效果拉~

自己实现一个PullToZoomListView_第1张图片

实现原理:

我是先看一github的源码,github地址: https://github.com/Frank-Zhu/PullZoomView
首先,讲一下源码的实现原理把,因为我是在源码的基础上自己写的,实现原理都是一样的~

1.定义接口,IPullToZoom.java,

来看一下接口都有哪些方法:
自己实现一个PullToZoomListView_第2张图片
看方法名就知道意思了吧。
一共有三个view,分别是rootView,headerView,zoomView,
其中,rootView的类型是不固定的,因为rootView就是我们要实现可以下拉放大头部的view呀~可以是listview,scrollView等,今天我们说的是listView,因为原理都是一样的,一通百通啦~
headerView指的是上图中的登录注册的两个按钮那一部分,看效果可以发现,它一直附着在放大的部件的底部,但是本身大小并没有发生变化
zoomView指的就是下拉被放大的那一部分,对照着效果图指的就是被放大的图片的那一部分啦

2.定义抽象类PullToZoomBase.java

自己实现一个PullToZoomListView_第3张图片

这个抽象类继承了 LinearLayout,并且实现了上面的IPullToZoom接口



我们主要讲解它的主要的几个方法

init()


   private void init(Context context, AttributeSet attrs) {
        setGravity(Gravity.CENTER);

        ViewConfiguration config = ViewConfiguration.get(context);
        mTouchSlop = config.getScaledTouchSlop();

        DisplayMetrics localDisplayMetrics = new DisplayMetrics();
        ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(localDisplayMetrics);
        mScreenHeight = localDisplayMetrics.heightPixels;
        mScreenWidth = localDisplayMetrics.widthPixels;

        // Refreshable View
        // By passing the attrs, we can add ListView/GridView params via XML
        mRootView = createRootView(context, attrs);

        if (attrs != null) {
            LayoutInflater mLayoutInflater = LayoutInflater.from(getContext());
            //初始化状态View
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.PullToZoomView);

            int zoomViewResId = a.getResourceId(R.styleable.PullToZoomView_zoomView, 0);
            if (zoomViewResId > 0) {
                mZoomView = mLayoutInflater.inflate(zoomViewResId, null, false);
            }

            int headerViewResId = a.getResourceId(R.styleable.PullToZoomView_headerView, 0);
            if (headerViewResId > 0) {
                mHeaderView = mLayoutInflater.inflate(headerViewResId, null, false);
            }

            isParallax = a.getBoolean(R.styleable.PullToZoomView_isHeaderParallax, true);

            // Let the derivative classes have a go at handling attributes, then
            // recycle them...
            handleStyledAttributes(a);
            a.recycle();
        }
        addView(mRootView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    }

我们看这个方法,它主要做了什么事呢?
(1)通过creatRootView方法得到一个rootView,这个creatRootView方法是一个抽象方法
(2)通过attr中定义的styleable得到headerView和zoomView的ResId并且加载出来(if (attrs != null) 中的内容)
(3)通过handleStyledAttributes(a)抽象方法把headerView和zoomView添加到LinearLayout中
(3)通过addView把rootView添加到LinearLayout中(最后一行)

onInterceptTouchEvent()


   @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!isPullToZoomEnabled() || isHideHeader()) {
            return false;
        }

        final int action = event.getAction();

        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            mIsBeingDragged = false;
            return false;
        }

        if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
            return true;
        }
        switch (action) {
            case MotionEvent.ACTION_MOVE: {
                if (isReadyForPullStart()) {
                    final float y = event.getY(), x = event.getX();
                    final float diff, oppositeDiff, absDiff;

                    // We need to use the correct values, based on scroll
                    // direction
                    diff = y - mLastMotionY;
                    oppositeDiff = x - mLastMotionX;
                    absDiff = Math.abs(diff);

                    if (absDiff > mTouchSlop && absDiff > Math.abs(oppositeDiff)) {
                        if (diff >= 1f && isReadyForPullStart()) {
                            mLastMotionY = y;
                            mLastMotionX = x;
                            mIsBeingDragged = true;
                        }
                    }
                }
                break;
            }
            case MotionEvent.ACTION_DOWN: {
                if (isReadyForPullStart()) {
                    mLastMotionY = mInitialMotionY = event.getY();
                    mLastMotionX = mInitialMotionX = event.getX();
                    mIsBeingDragged = false;
                }
                break;
            }
        }

        return mIsBeingDragged;
    }

我们通过抽象方法isReadyForFullStart来判断当前是否可以进行下拉放大操作,放在listView中就是要判断当前listView的第一个item是否可见

onTouchEvent()


  @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        if (!isPullToZoomEnabled() || isHideHeader()) {
            return false;
        }

        if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
            return false;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE: {
                if (mIsBeingDragged) {
                    mLastMotionY = event.getY();
                    mLastMotionX = event.getX();
                    pullEvent();
                    isZooming = true;
                    return true;
                }
                break;
            }

            case MotionEvent.ACTION_DOWN: {
                if (isReadyForPullStart()) {
                    mLastMotionY = mInitialMotionY = event.getY();
                    mLastMotionX = mInitialMotionX = event.getX();
                    return true;
                }
                break;
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                if (mIsBeingDragged) {
                    mIsBeingDragged = false;
                    // If we're already refreshing, just scroll back to the top
                    if (isZooming()) {
                        smoothScrollToTop();
                        if (onPullZoomListener != null) {
                            onPullZoomListener.onPullZoomEnd();
                        }
                        isZooming = false;
                        return true;
                    }
                    return true;
                }
                break;
            }
        }
        return false;
    }

在触摸事件处理中,在Action_Move中,如果满足isBeingDragged,进行抽象方法pullEvent,这个抽象方法就是进行具体的下拉放大操作啦~
在Action_Up,cancel中,如果下拉放大了,松开手我们要把头还原到原来的样子,这是通过smoothScrollToTop()方法来实现的,这也是一个抽象方法

3.实现PullToZoomListViewEx.java

自己实现一个PullToZoomListView_第4张图片

它继承了PullToZoomBase

重要的几个方法:

1.isReadyForPullStart()

 @Override
    protected boolean isReadyForPullStart() {
        return isFirstItemVisible();
    }

    private boolean isFirstItemVisible() {
        final Adapter adapter = mRootView.getAdapter();

        if (null == adapter || adapter.isEmpty()) {
            return true;
        } else {
            /**
             * This check should really just be:
             * mRootView.getFirstVisiblePosition() == 0, but PtRListView
             * internally use a HeaderView which messes the positions up. For
             * now we'll just add one to account for it and rely on the inner
             * condition which checks getTop().
             */
            if (mRootView.getFirstVisiblePosition() <= 1) {
                final View firstVisibleChild = mRootView.getChildAt(0);
                if (firstVisibleChild != null) {
                    return firstVisibleChild.getTop() >= mRootView.getTop();
                }
            }
        }

        return false;
    }
可以看到,它就是判断了firstVisible

2.class ScaleRunnable

  class ScalingRunnable implements Runnable {
        protected long mDuration;
        protected boolean mIsFinished = true;
        protected float mScale;
        protected long mStartTime;

        ScalingRunnable() {
        }

        public void abortAnimation() {
            mIsFinished = true;
        }

        public boolean isFinished() {
            return mIsFinished;
        }

        public void run() {
            if (mZoomView != null) {
                float f2;
                ViewGroup.LayoutParams localLayoutParams;
                if ((!mIsFinished) && (mScale > 1.0D)) {
                    float f1 = ((float) SystemClock.currentThreadTimeMillis() - (float) mStartTime) / (float) mDuration;
                    f2 = mScale - (mScale - 1.0F) * PullToZoomListViewEx.sInterpolator.getInterpolation(f1);
                    localLayoutParams = mHeaderContainer.getLayoutParams();
                    Log.d(TAG, "ScalingRunnable --> f2 = " + f2);
                    if (f2 > 1.0F) {
                        localLayoutParams.height = ((int) (f2 * mHeaderHeight));
                        mHeaderContainer.setLayoutParams(localLayoutParams);
                        post(this);
                        return;
                    }
                    mIsFinished = true;
                }
            }
        }

        public void startAnimation(long paramLong) {
            if (mZoomView != null) {
                mStartTime = SystemClock.currentThreadTimeMillis();
                mDuration = paramLong;
                mScale = ((float) (mHeaderContainer.getBottom()) / mHeaderHeight);
                mIsFinished = false;
                post(this);
            }
        }
    }


/*****************************************************************************分割线**************************************************************************************************************/

以上都是github源码的分析,自己实现的话,我并没有这样层层封装,因为我并没有想要扩展,只是为了弄明白哈哈~

下面看看我的实现把~

CustomPulToZoomListiew

package com.example.myapp.view;

import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;

import com.example.myapp.R;
import com.example.myapp.util.Methods;

/**
 * Created by zyr
 * DATE: 16-3-28
 * Time: 下午2:58
 * Email: [email protected]
 * github:
 */
public class CustomPullToZoomListView extends LinearLayout {
    private Context mContext;
    /***************** View*********************/
    private FrameLayout mHeaderContainer;
    private View mHeadView;//不可拉伸的那部分
    private View mZoomView;//可拉伸的那个view
    private ListView mListView;
    private int mHeadViewId,mZoomViewId;
    ViewGroup.LayoutParams layoutParams;
    private int mHeaderContainerOriHeight;
    /***************** 状态*********************/
    private boolean isBeingDragged;
    private boolean isZooming;

    private int mDownX,mDownY,mMoveX,mMoveY,deltaX,deltaY;
    private int mScreenHeight,mScreenWidth;
    public final static int MIN_MOVE_Y = 50;

    public CustomPullToZoomListView(Context context) {
        this(context, null);
    }

    public CustomPullToZoomListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomPullToZoomListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CustomPullToZoomListView);
        for(int i=0;i<typedArray.length();i++){
            int attr = typedArray.getIndex(i);
            switch (attr){
                case R.styleable.CustomPullToZoomListView_headerLayout:
                    mHeadViewId = typedArray.getResourceId(R.styleable.CustomPullToZoomListView_headerLayout,0);
                    break;
                case R.styleable.CustomPullToZoomListView_zoomLayout:
                    mZoomViewId = typedArray.getResourceId(R.styleable.CustomPullToZoomListView_zoomLayout,0);
                    break;
            }
        }
        mHeadView = LayoutInflater.from(mContext).inflate(mHeadViewId,null);
        mZoomView = LayoutInflater.from(mContext).inflate(mZoomViewId, null);
        mHeaderContainer = new FrameLayout(mContext);
        mListView = new ListView(mContext);

        DisplayMetrics localDisplayMetrics = new DisplayMetrics();
        ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(localDisplayMetrics);
        mScreenHeight = localDisplayMetrics.heightPixels;
        mScreenWidth = localDisplayMetrics.widthPixels;
        init();
    }

    private void init() {
        setOrientation(VERTICAL);
        setGravity(Gravity.CENTER);
        mHeaderContainer.addView(mZoomView);
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        lp.gravity = Gravity.BOTTOM;
        mHeaderContainer.addView(mHeadView, lp);
        mHeaderContainer.setMinimumHeight(Methods.computePixelsWithDensity(mContext,200));
        addView(mHeaderContainer);
        addView(mListView);
        layoutParams = mHeaderContainer.getLayoutParams();

        mHeaderContainer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mHeaderContainerOriHeight = mHeaderContainer.getHeight();
                Log.d("zyr", "mHeaderContainerOriHeight :" + mHeaderContainerOriHeight);
                mHeaderContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });

    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                mDownX = (int)ev.getX();
                mDownY = (int)ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                mMoveX = (int)ev.getX();
                mMoveY = (int)ev.getY();
                deltaX = mMoveX - mDownX;
                deltaY = mMoveY - mDownY;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                    break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d("zyr","INT ACTION_DOWN");
                if(isReadyForPullStart()){
                    return false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d("zyr","INT ACTION_MOVE");
                if(isReadyForPullStart()){
                    //y向滑动
                    //y向滑动超过一定距离
                    if(Math.abs(deltaY) > Math.abs(deltaX)
                            && Math.abs(deltaY) > MIN_MOVE_Y
                            && deltaY > 0){
                        isBeingDragged = true;
                        return true;
                    }else{
                        isBeingDragged = false;
                    }
                }
                break;
        }

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d("zyr","TOU ACTION_DOWN");
                if(isReadyForPullStart()){
                    isBeingDragged = true;
                    return true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d("zyr","TOU ACTION_MOVE");
                if(isBeingDragged && deltaY > 0){
                    pullEvent();
                    isZooming = true;
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if(isBeingDragged && isZooming){
                    autoScrollToOrig();
                }
                isBeingDragged = false;
                isZooming = false;
                break;
        }
        return super.onTouchEvent(event);
    }

    private void autoScrollToOrig() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(mHeaderContainerOriHeight+deltaY,mHeaderContainerOriHeight);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (Integer)animation.getAnimatedValue();
                Log.d("zyr","value:" + value);
                layoutParams.height = value;
                mHeaderContainer.setLayoutParams(layoutParams);
            }
        });
        valueAnimator.setDuration(100);
        valueAnimator.start();
    }

    private void pullEvent() {
//        Log.d("zyr","pullEvent deltaY:" + deltaY);
        layoutParams.height = mHeaderContainerOriHeight + deltaY > mScreenHeight*3/4 ? mScreenHeight*3/4 : mHeaderContainerOriHeight + deltaY;
        mHeaderContainer.setLayoutParams(layoutParams);
    }

    private boolean isReadyForPullStart() {
        if(mListView.getFirstVisiblePosition() == 0){
            return true;
        }else{
            return false;
        }
    }

    public void setAdapter(ListAdapter adapter){
        mListView.setAdapter(adapter);
    }

    public void setOnItemClickListener(AdapterView.OnItemClickListener onItemClickListener){
        mListView.setOnItemClickListener(onItemClickListener);
    }
}





你可能感兴趣的:(自己实现一个PullToZoomListView)