您的位置:首页 > 移动开发 > Android开发

自己实现一个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源码的分析,自己实现的话,我并没有这样层层封装,因为我并没有想要扩展,只是为了弄明白哈哈~

下面看看我的实现把~

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"/>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息