上效果拉~
实现原理:
我是先看一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添加到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
它继承了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);
}
}