自己实现一个PullToZoomListView放大回弹效果,PullToZoomView源码解析
2016-03-29 11:00
781 查看
上效果拉~
实现原理:
我是先看一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源码的分析,自己实现的话,我并没有这样层层封装,因为我并没有想要扩展,只是为了弄明白哈哈~
下面看看我的实现把~
CustomPulToZoomListiewpackage 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"/>
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories