本文介绍PullZoomView的简单实现,如图:
就是通过下拉ListView或者ScrollView或者更多的View如GridView,RecycleView等等,的时候对Header有一个放大缩小的效果
实现思路就是根据所需要封装的不同的下拉控件来做不同的实现,比如:
ListView:该控件本身有添加Header的功能,我们只需做简单的处理就可以用了,在满足一定条件时做事件拦截,让整个控件向下滚动的时候回传一个value用来改变Header的高度。
ScrollView:这就需要我们自己封装一个Header在ScrollView的孩子控件当中。滚动的时候和ListView做相同的操作即可。
IPullZoom 定义公共接口
PullZoomBase 抽象公共的方法
PullZoomListView ListView的实现
PullZoomScrollView ScrollView的实现
IPullZoom.java
public interface IPullZoom { void initHeader(TypedArray a); }
public abstract class PullZoomBase<T extends View> extends LinearLayout implements IPullZoom { /** * 根布局,用来装所有内容 */ protected T mRootView; /** * 定义的显示伸缩效果的View */ protected View mZoomView; /** * 伸缩效果上展示的内容 */ protected View mHeadView; /** * 是否允许下拉 */ private boolean isPullEnable = true; private boolean isZooming; private boolean isHeadHide; private boolean isDragging; private float mLastX; private float mLastY; private float mInitX; private float mInitY; private int mTouchSlop; public PullZoomBase(Context context) { this(context, null); } public PullZoomBase(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PullZoomBase(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ViewConfiguration config = ViewConfiguration.get(context); mTouchSlop = config.getScaledTouchSlop(); mRootView = initRootView(context, attrs); LayoutInflater inflater = LayoutInflater.from(context); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PullZoomView); int zoomResId = a.getResourceId(R.styleable.PullZoomView_zoomview, 0); if (zoomResId > 0) { mZoomView = inflater.inflate(zoomResId, null, false); } int headResId = a.getResourceId(R.styleable.PullZoomView_headview, 0); if (headResId > 0) { mHeadView = inflater.inflate(headResId, null, false); } initHeader(a); a.recycle(); addView(mRootView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (!isPullEnable() || isHeadHide()) { return false; } int action = ev.getAction(); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { isDragging = false; return false; } if (action != MotionEvent.ACTION_DOWN && isDragging) { return true; } switch (action) { case MotionEvent.ACTION_DOWN: if (allowStart()) { mLastX = mInitX = ev.getX(); mLastY = mInitY = ev.getY(); isDragging = false; } break; case MotionEvent.ACTION_MOVE: if (allowStart()) { float x = ev.getX(); float y = ev.getY(); float diffX = x - mLastX; float diffY = y - mLastY; float diffYAds = Math.abs(diffY); if (diffYAds > mTouchSlop && diffYAds > Math.abs(diffX)) { if (diffY >= 1 && allowStart()) { mLastX = x; mLastY = y; isDragging = true; } } } break; } return isDragging; } @Override public boolean onTouchEvent(MotionEvent event) { if (!isPullEnable || isHeadHide()) { return false; } if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (allowStart()) { mLastX = mInitX = event.getX(); mLastY = mInitY = event.getY(); return true; } break; case MotionEvent.ACTION_MOVE: if (allowStart()) { mLastX = event.getX(); mLastY = event.getY(); final int newScrollValue = Math.round(Math.min(mInitY - mLastY, 0) / 2.0f); pull(newScrollValue); return true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (isDragging) { isDragging = false; smoothRestore(); } break; } return false; } public boolean isPullEnable() { return isPullEnable; } public void setIsPullEnable(boolean isPullEnable) { this.isPullEnable = isPullEnable; } public boolean isHeadHide() { return isHeadHide; } public void setIsHeadHide(boolean isHeadHide) { this.isHeadHide = isHeadHide; } /** * 创建根布局,例如ListView,GridView,RecycleView,ScrollView等等 * * @param context * @param set * @return */ public abstract T initRootView(Context context, AttributeSet set); /** * 判定是否允许开始滚动 * * @return */ public abstract boolean allowStart(); /** * 传入一个计算值,用来对Header做放大缩小操作 * * @param value */ public abstract void pull(int value); /** * */ public abstract void smoothRestore(); }
PullZoomListView.java
public class PullZoomListView extends PullZoomBase<ListView> { private FrameLayout mHeaderContainer; private int mHeaderHeight; private SmoothRestore mSmoothRestore; public static Interpolator mInterpolator = new Interpolator() { @Override public float getInterpolation(float input) { float f = input - 1.0F; return 1.0F + f * (f * (f * (f * f))); } }; public PullZoomListView(Context context) { this(context, null); } public PullZoomListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PullZoomListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mSmoothRestore = new SmoothRestore(); } @Override public ListView initRootView(Context context, AttributeSet set) { ListView listview = new ListView(context, set); return listview; } @Override public void initHeader(TypedArray a) { mHeaderContainer = new FrameLayout(getContext()); if (mZoomView != null) { mHeaderContainer.addView(mZoomView); } if (mHeadView != null) { mHeaderContainer.addView(mHeadView); } mRootView.addHeaderView(mHeaderContainer); } public void setAdapter(BaseAdapter adapter) { mRootView.setAdapter(adapter); } public void setHeaderLayoutParams(AbsListView.LayoutParams params) { if (mHeaderContainer != null) { mHeaderContainer.setLayoutParams(params); mHeaderHeight = params.height; } } public void updateHeader() { if (mHeaderContainer != null) { mRootView.removeHeaderView(mHeaderContainer); mHeaderContainer.removeAllViews(); if (mZoomView != null) { mHeaderContainer.addView(mZoomView); } if (mHeadView != null) { mHeaderContainer.addView(mHeadView); } mHeaderHeight = mHeaderContainer.getHeight(); mRootView.addHeaderView(mHeaderContainer); } } @Override public boolean allowStart() { return isFirstItemVisiable(); } private boolean isFirstItemVisiable() { Adapter adapter = mRootView.getAdapter(); if (null == adapter || adapter.isEmpty()) { return true; } else { if (mRootView.getFirstVisiblePosition() <= 1) { View view = mRootView.getChildAt(0); if (view != null) { return view.getTop() >= mRootView.getTop(); } } } return false; } @Override public void pull(int value) { if (mSmoothRestore != null && !mSmoothRestore.isFinish()) { mSmoothRestore.abort(); } ViewGroup.LayoutParams params = mHeaderContainer.getLayoutParams(); params.height = Math.abs(value) + mHeaderHeight; mHeaderContainer.setLayoutParams(params); } @Override public void smoothRestore() { mSmoothRestore.start(200L); } class SmoothRestore implements Runnable { protected long duration; protected boolean isFinished; protected float scale; protected long starttime; SmoothRestore() { } public void abort() { isFinished = true; } public boolean isFinish() { return isFinished; } public void start(long d) { if (mZoomView != null) { starttime = SystemClock.currentThreadTimeMillis(); duration = d; scale = (float) mHeaderContainer.getBottom() / mHeaderHeight; isFinished = false; post(this); } } @Override public void run() { if (mZoomView != null) { float f2; ViewGroup.LayoutParams params; if (!isFinished && scale > 1.0D) { float f1 = ((float) SystemClock.currentThreadTimeMillis() - (float) starttime) / (float) duration; f2 = scale - (scale - 1.0F) * PullZoomListView.mInterpolator.getInterpolation(f1); params = mHeaderContainer.getLayoutParams(); if (f2 > 1.0F) { params.height = (int) (f2 * mHeaderHeight); mHeaderContainer.setLayoutParams(params); post(this); return; } isFinished = true; } } } } }