自己实现一个PullToZoomListView放大回弹效果,PullToZoomView源码解析

上效果拉~


实现原理:

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

1.定义接口,IPullToZoom.java,

来看一下接口都有哪些方法:

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

2.定义抽象类PullToZoomBase.java



这个抽象类继承了 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添加到mHeaderContainer中
(4)通过addHeaderView把mHeaderContainer添加到listview的header中
(5)通过addView把listview添加到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



它继承了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.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
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.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Scroller;

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

/**
 * Created by zyr
 * DATE: 16-3-28
 * Time: 下午2:58
 * Email: yanru.zhang@renren-inc.com
 * github:
 */
public class CustomPullToZoomListView extends ListView implements AbsListView.OnScrollListener{
    private Context mContext;
    /***************** View*********************/
    private View mHeaderRootView;
    private FrameLayout mHeaderContainer;
    private View mHeadView;//不可拉伸的那部分
    private View mZoomView;//可拉伸的那个view
    private int mHeadViewId,mZoomViewId;
    FrameLayout.LayoutParams mHeaderContainerLp;
    private int mHeaderContainerOriHeight;
    private Scroller mScroller;
    /***************** 状态*********************/
    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 final static int SCROLL_DURATION = 200;


    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;
            }
        }

        initScroller();

        mHeaderRootView = LayoutInflater.from(mContext).inflate(R.layout.pull_to_zoom_header_layout,null);
        mHeaderContainer = (FrameLayout) mHeaderRootView.findViewById(R.id.pull_to_zoom_header_content_ly);
        mHeaderContainerLp = (FrameLayout.LayoutParams)mHeaderContainer.getLayoutParams();
        if(mHeaderContainerLp == null){
            mHeaderContainerLp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
        }
        //给listview添加onScroll监听
        setOnScrollListener(this);
        addHeaderView(mHeaderRootView, null, false);

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

    private void initScroller() {
        mScroller = new Scroller(mContext,new DecelerateInterpolator());
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(mScroller.computeScrollOffset()){
            mHeaderContainerLp.height = mScroller.getCurrY();
            mHeaderContainer.setLayoutParams(mHeaderContainerLp);
        }
    }

    private void init() {
        mHeadView = LayoutInflater.from(mContext).inflate(mHeadViewId,null);
        mZoomView = LayoutInflater.from(mContext).inflate(mZoomViewId, null);

        mHeaderContainer.addView(mZoomView);
        FrameLayout.LayoutParams mHeaderViewlp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        mHeaderViewlp.gravity = Gravity.BOTTOM;
        mHeaderContainer.addView(mHeadView, mHeaderViewlp);
        mHeaderContainer.setMinimumHeight(Methods.computePixelsWithDensity(mContext, 200));

        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 onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d("zyr","TOU ACTION_DOWN");
                mDownX = (int) event.getX();
                mDownY = (int) event.getY();
                if(isReadyForPullStart()){
                    Log.d("zyr","--------------isReadyForPullStart");
                    isBeingDragged = true;
                }else{
                    isBeingDragged = false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d("zyr","TOU ACTION_MOVE");
                mMoveX = (int) event.getX();
                mMoveY = (int) event.getY();
                deltaX = mMoveX - mDownX;
                deltaY = mMoveY - mDownY;
                if(isReadyForPullStart() && isBeingDragged && deltaY > 0){
                    Log.d("zyr","--------------isReadyForPullStart");
                    pullEvent();
                    isZooming = true;
                }else{
                    isZooming = false;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if(isBeingDragged && isZooming){
                    autoScrollToOrig();
                    isBeingDragged = false;
                    isZooming = false;
                    return true;
                }
                break;
        }
        return super.onTouchEvent(event);
    }

    private void autoScrollToOrig() {
        mScroller.startScroll(0,mHeaderContainerLp.height,0,mHeaderContainerOriHeight - mHeaderContainerLp.height,SCROLL_DURATION);
        postInvalidate();
    }

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

    private boolean isReadyForPullStart() {
        Log.e("zyr", "mHeaderContainer.getBottom():" + mHeaderContainer.getBottom());
        if(mHeaderRootView.getBottom() >= mHeaderContainerOriHeight){
            return true;
        }else{
            return false;
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        Log.d("zyr", "onScrollStateChanged --> ");
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        Log.d("zyr", "onScroll --> ");

    }
}


 
 
activty:
package com.example.myapp.activity;

import android.view.View;
import android.widget.AdapterView;
import android.widget.TextView;

import com.example.myapp.R;
import com.example.myapp.adapter.CommonAdapter;
import com.example.myapp.util.Methods;
import com.example.myapp.view.CustomPullToRefreshListView;
import com.example.myapp.view.CustomPullToZoomListView;

import java.util.ArrayList;

/**
 * Created by zyr
 * DATE: 16-3-25
 * Time: 下午5:24
 * Email: yanru.zhang@renren-inc.com
 */
public class PullToZoomViewTestActivity extends BaseActivity {
    private CustomPullToZoomListView customPullToZoomListView;
    private TextView signUpBtn;
    private TextView loginBtn;
    private CommonAdapter commonAdapter;
    private ArrayList<String> strings  = new ArrayList<>();
    @Override
    protected void initView() {
        customPullToZoomListView = (CustomPullToZoomListView)findViewById(R.id.pull_to_zoom);
        for(int i=0;i<20;i++){
            strings.add("zyr" + i);
        }
        commonAdapter = new CommonAdapter(this,strings);
        customPullToZoomListView.setAdapter(commonAdapter);
        customPullToZoomListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Methods.toast(mContext,strings.get((int)id));
            }
        });

        signUpBtn = (TextView) findViewById(R.id.sign_up);
        loginBtn = (TextView) findViewById(R.id.login);
    }

    @Override
    protected int onSetContainerViewId() {
        return R.layout.activity_pull_to_zoom_view_layout;
    }

    @Override
    public void initListener() {
        signUpBtn.setOnClickListener(this);
        loginBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.sign_up:
                Methods.toast(mContext,"sign up");
                break;
            case R.id.login:
                Methods.toast(mContext,"login");
                break;
        }
    }
}

xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.myapp.view.CustomPullToZoomListView
        android:id="@+id/pull_to_zoom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:headerLayout="@layout/layout_pull_to_zoom_header"
        app:zoomLayout="@layout/layout_pull_to_zoom_zoom">
    </com.example.myapp.view.CustomPullToZoomListView>
</LinearLayout>

pull_to_zoom_header_layout.xml
 
   
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <FrameLayout android:id="@+id/pull_to_zoom_header_content_ly"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"></FrameLayout>
</FrameLayout>


header:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="50dp">
    <TextView android:id="@+id/sign_up"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:text="SignUp"
        android:layout_weight="1"
        android:layout_margin="1dp"
        android:gravity="center"
        android:background="@color/trans_black"
        android:textSize="20sp"
        android:textColor="@color/white"/>
    <TextView android:id="@+id/login"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:text="LoGin"
        android:layout_weight="1"
        android:layout_margin="1dp"
        android:gravity="center"
        android:background="@color/trans_black"
        android:textSize="20sp"
        android:textColor="@color/white"/>
</LinearLayout>

zoom:

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/splash01"
    android:scaleType="centerCrop"
    android:layout_gravity="center_horizontal"/>





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值