我们先看效果图:

这是仿苹果app的一种下拉head拉伸的效果,这种弹性伸缩能给用户一种良好的体验,因此我们在各大主流app上也能看的到这种效果,而PullZoomView是一款在安卓能实现上述效果并且能实现视差效果的开源框架。今天让我们来对此框架一探究竟!

先来看看项目结构:

此项目包括了:IPollToZoom,一个接口类定义了一些供实例对象调用的方法;  PullToZoomBase,一个基类定义了一些公共方法;然后

PullToZoomListViewEx 与 PullToZoomScrollViewEx 分别是继承base所派生的两个子类。

那么我们就先来看看PullToZoomBase基类:

package com.yinbaner.view;import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.LinearLayout;import com.yinbaner.wifibox.R;public abstract class PullToZoomBase<T extends View> extends LinearLayout implements IPullToZoom<T> {private static final float FRICTION = 2.0f;protected T mRootView;protected View mHeaderView;//头部Viewprotected View mZoomView;//缩放拉伸Viewprotected int mScreenHeight;protected int mScreenWidth;private boolean isZoomEnabled = true;private boolean isParallax = true;private boolean isZooming = false;private boolean isHideHeader = false;private int mTouchSlop;private boolean mIsBeingDragged = false;private float mLastMotionY;private float mLastMotionX;private float mInitialMotionY;private OnPullZoomListener onPullZoomListener;public PullToZoomBase(Context context) {this(context, null);}public PullToZoomBase(Context context, AttributeSet attrs) {super(context, attrs);init(context, attrs);}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; //屏幕像素hmScreenWidth = localDisplayMetrics.widthPixels; //屏幕像素w// Refreshable View// By passing the attrs, we can add ListView/GridView params via XMLmRootView = createRootView(context, attrs); //抽象方法,子类实现返回子类创建的mRootViewif (attrs != null) {LayoutInflater mLayoutInflater = LayoutInflater.from(getContext());//初始化状态ViewTypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.PullToZoomView);int zoomViewResId = a.getResourceId(R.styleable.PullToZoomView_zoomView, 0);//获得xml中定义的zommView IDif (zoomViewResId > 0) {mZoomView = mLayoutInflater.inflate(zoomViewResId, null, false);}int headerViewResId = a.getResourceId(R.styleable.PullToZoomView_headerView, 0);//获得xml中定义的headerView IDif (headerViewResId > 0) {mHeaderView = mLayoutInflater.inflate(headerViewResId, null, false);}isParallax = a.getBoolean(R.styleable.PullToZoomView_isHeaderParallax, true);//获得xml中定义的是否支持视差// Let the derivative classes have a go at handling attributes, then// recycle them...handleStyledAttributes(a);//传入TypedArray,让子类获取到xml中定义的contentView IDa.recycle();}addView(mRootView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);}public void setOnPullZoomListener(OnPullZoomListener onPullZoomListener) {this.onPullZoomListener = onPullZoomListener;}@Overridepublic T getPullRootView() {return mRootView;}@Overridepublic View getZoomView() {return mZoomView;}@Overridepublic View getHeaderView() {return mHeaderView;}@Overridepublic boolean isPullToZoomEnabled() {return isZoomEnabled;}@Overridepublic boolean isZooming() {return isZooming;}@Overridepublic boolean isParallax() {return isParallax;}@Overridepublic boolean isHideHeader() {return isHideHeader;}public void setZoomEnabled(boolean isZoomEnabled) {this.isZoomEnabled = isZoomEnabled;}public void setParallax(boolean isParallax) {this.isParallax = isParallax;}public void setHideHeader(boolean isHideHeader) {//header显示才能Zoomthis.isHideHeader = isHideHeader;}private VelocityTracker mVelocityTracker;@Overridepublic 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// directiondiff = 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();mIsBeingDragged = false;}break;}}return mIsBeingDragged;}@Overridepublic boolean onTouchEvent( MotionEvent event) {if (!isPullToZoomEnabled() || isHideHeader()) {return false;}if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {//getEdgeFlags触到边缘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();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 topif (isZooming()) {//                  ↑//我发现这个注释是经典的下拉刷新框架PulToRefresh里面的,由此可以看出此框架有借鉴了PulToRefresh的思想smoothScrollToTop();//调用子类的缩回逻辑if (onPullZoomListener != null) {onPullZoomListener.onPullZoomEnd();}isZooming = false;return true;}return true;}break;}}return false;}private void pullEvent() {final int newScrollValue;final float initialMotionValue, lastMotionValue;initialMotionValue = mInitialMotionY; //开始按下的Y坐标lastMotionValue = mLastMotionY;//滑动移动的Y坐标//当放大head时候,随时的计算手指滑动的 y距离/2newScrollValue = Math.round(Math.min(initialMotionValue - lastMotionValue, 0) / FRICTION);//调用子类放大逻辑,传入差值pullHeaderToZoom(newScrollValue);if (onPullZoomListener != null) {onPullZoomListener.onPullZooming(newScrollValue);}}protected abstract void pullHeaderToZoom(int newScrollValue);public abstract void setHeaderView(View headerView);public abstract void setZoomView(View zoomView);protected abstract T createRootView(Context context, AttributeSet attrs);protected abstract void smoothScrollToTop();protected abstract boolean isReadyForPullStart();public interface OnPullZoomListener {void onPullZooming(int newScrollValue);void onPullZoomEnd();}
}

必要的地方都做了注释,大体上是实现了接口,重写了一些外部操作的方法,定义了一些抽象方法,用于从子类获取到返回的结果,并且根据一些条件做了事件的拦截和处理和通过MotionEvent计算出处于顶部的时候下拉的距离,传给子类做head高度拉伸处理。

再来看看 PullToZoomScrollViewEx

package com.yinbaner.view;import android.content.Context;
import android.content.res.TypedArray;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;import com.yinbaner.wifibox.R;public class PullToZoomScrollViewEx extends PullToZoomBase<ScrollView> {private static final String TAG = PullToZoomScrollViewEx.class.getSimpleName();private boolean isCustomHeaderHeight = false;private FrameLayout mHeaderContainer;private LinearLayout mRootContainer;private View mContentView;private int mHeaderHeight;private ScalingRunnable mScalingRunnable;private static final Interpolator sInterpolator = new Interpolator() {@Overridepublic float getInterpolation(float paramAnonymousFloat) {float f = paramAnonymousFloat - 1.0F;return 1.0F + f * (f * (f * (f * f)));}};public PullToZoomScrollViewEx(Context context) {this(context, null);}public PullToZoomScrollViewEx(Context context, AttributeSet attrs) {super(context, attrs);mScalingRunnable = new ScalingRunnable();((InternalScrollView) mRootView).setOnScrollViewChangedListener(new OnScrollViewChangedListener() {@Overridepublic void onInternalScrollChanged(int left, int top, int oldLeft, int oldTop) {if (isPullToZoomEnabled() && isParallax()) {//视差效果实现代码块,即是滑动scrollView的时候同时移动mHeaderContainerLog.d(TAG, "onScrollChanged --> getScrollY() = " + mRootView.getScrollY());float f = mHeaderHeight - mHeaderContainer.getBottom() + mRootView.getScrollY();Log.d(TAG, "onScrollChanged --> f = " + f);if(osc != null)osc.OnScrollChangeListener(f);//若实现了监听,则回调if ((f > 0.0F) && (f < mHeaderHeight)) {int i = (int) (0.65D * f);mHeaderContainer.scrollTo(0, -i);//两者移动距离之商为0.65,所以产生视差效果} else if (mHeaderContainer.getScrollY() != 0) {mHeaderContainer.scrollTo(0, 0);//移动mHeaderContainer}}}@Overridepublic void OnScrollChangeListener(float f) {// TODO Auto-generated method stub}});}@Overrideprotected void pullHeaderToZoom(int newScrollValue) {//放大逻辑Log.d(TAG, "pullHeaderToZoom --> newScrollValue = " + newScrollValue);Log.d(TAG, "pullHeaderToZoom --> mHeaderHeight = " + mHeaderHeight);if (mScalingRunnable != null && !mScalingRunnable.isFinished()) {mScalingRunnable.abortAnimation();}//,根据传入的差值动态改变高度ViewGroup.LayoutParams localLayoutParams = mHeaderContainer.getLayoutParams(); //获取到mHeaderContainer的LayoutParamslocalLayoutParams.height = Math.abs(newScrollValue) + mHeaderHeight;//改变高度mHeaderContainer.setLayoutParams(localLayoutParams);//设置新的LayoutParamsif (isCustomHeaderHeight) {ViewGroup.LayoutParams zoomLayoutParams = mZoomView.getLayoutParams();zoomLayoutParams.height = Math.abs(newScrollValue) + mHeaderHeight;mZoomView.setLayoutParams(zoomLayoutParams);}}/*** 是否显示headerView** @param isHideHeader true: show false: hide*/@Overridepublic void setHideHeader(boolean isHideHeader) {if (isHideHeader != isHideHeader() && mHeaderContainer != null) {super.setHideHeader(isHideHeader);if (isHideHeader) {mHeaderContainer.setVisibility(GONE);} else {mHeaderContainer.setVisibility(VISIBLE);}}}/*** 动态设置headerView** @param headerView*/@Overridepublic void setHeaderView(View headerView) {if (headerView != null) {mHeaderView = headerView;updateHeaderView();}}/*** 动态设置zoomView** @param zoomView*/@Overridepublic void setZoomView(View zoomView) {if (zoomView != null) {mZoomView = zoomView;updateHeaderView();}}/*** 更新HeaderView*/private void updateHeaderView() {if (mHeaderContainer != null) {mHeaderContainer.removeAllViews();if (mZoomView != null) {mHeaderContainer.addView(mZoomView);}if (mHeaderView != null) {mHeaderContainer.addView(mHeaderView);}}}/*** 动态设置contentView** @param contentView*/public void setScrollContentView(View contentView) {if (contentView != null) {if (mContentView != null) {mRootContainer.removeView(mContentView);}mContentView = contentView;mRootContainer.addView(mContentView);}}/*** 实现父类的抽象方法,用于返回scrollView给父类**/@Overrideprotected ScrollView createRootView(Context context, AttributeSet attrs) {ScrollView scrollView = new InternalScrollView(context, attrs);scrollView.setId(R.id.scrollview);return scrollView;}/*** 伸缩之后的回弹*/@Overrideprotected void smoothScrollToTop() {Log.d(TAG, "smoothScrollToTop --> ");mScalingRunnable.startAnimation(200L);}/*** 是否除于顶部*/@Overrideprotected boolean isReadyForPullStart() {return mRootView.getScrollY() == 0;}@Override //初始化viewpublic void handleStyledAttributes(TypedArray a) {mRootContainer = new LinearLayout(getContext());mRootContainer.setOrientation(LinearLayout.VERTICAL);mHeaderContainer = new FrameLayout(getContext());if (mZoomView != null) {mHeaderContainer.addView(mZoomView);}if (mHeaderView != null) {mHeaderContainer.addView(mHeaderView);}int contentViewResId = a.getResourceId(R.styleable.PullToZoomView_contentView, 0);if (contentViewResId > 0) {LayoutInflater mLayoutInflater = LayoutInflater.from(getContext());mContentView = mLayoutInflater.inflate(contentViewResId, null, false);}mRootContainer.addView(mHeaderContainer);if (mContentView != null) {mRootContainer.addView(mContentView);}mRootContainer.setClipChildren(false);//允许子View超出范围mHeaderContainer.setClipChildren(false);mRootView.addView(mRootContainer);}/*** 设置HeaderView高度** @param width* @param height*/public void setHeaderViewSize(int width, int height) {if (mHeaderContainer != null) {Object localObject = mHeaderContainer.getLayoutParams();if (localObject == null) {localObject = new ViewGroup.LayoutParams(width, height);}((ViewGroup.LayoutParams) localObject).width = width;((ViewGroup.LayoutParams) localObject).height = height;mHeaderContainer.setLayoutParams((ViewGroup.LayoutParams) localObject);mHeaderHeight = height;isCustomHeaderHeight = true;}}/*** 设置HeaderView LayoutParams** @param layoutParams LayoutParams*/public void setHeaderLayoutParams(LinearLayout.LayoutParams layoutParams) {if (mHeaderContainer != null) {mHeaderContainer.setLayoutParams(layoutParams);mHeaderHeight = layoutParams.height;isCustomHeaderHeight = true;}}@Overrideprotected void onLayout(boolean paramBoolean, int paramInt1, int paramInt2,int paramInt3, int paramInt4) {super.onLayout(paramBoolean, paramInt1, paramInt2, paramInt3, paramInt4);Log.d(TAG, "onLayout --> ");if (mHeaderHeight == 0 && mZoomView != null) {mHeaderHeight = mHeaderContainer.getHeight();}}/*** 通过调用 startAnimation* 来调用 ScalingRunnable的run()方法里面的回弹逻辑*/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) {if ((!mIsFinished) && (mScale > 1.0D)) {float f1 = ((float) SystemClock.currentThreadTimeMillis() - (float) mStartTime) / (float) mDuration;float f2 = mScale - (mScale - 1.0F) * PullToZoomScrollViewEx.sInterpolator.getInterpolation(f1);Log.d(TAG, "ScalingRunnable --> f2 = " + f2);if (f2 > 1.0F) {ViewGroup.LayoutParams localLayoutParams = mHeaderContainer.getLayoutParams();localLayoutParams.height = ((int) (f2 * mHeaderHeight));mHeaderContainer.setLayoutParams(localLayoutParams);//动态改变高度if (isCustomHeaderHeight) {ViewGroup.LayoutParams zoomLayoutParams = mZoomView.getLayoutParams();zoomLayoutParams.height = ((int) (f2 * mHeaderHeight));mZoomView.setLayoutParams(zoomLayoutParams);}post(this);return;}mIsFinished = true;}}}public void startAnimation(long paramLong) {//传入durationif (mZoomView != null) {mStartTime = SystemClock.currentThreadTimeMillis();mDuration = paramLong;mScale = ((float) (mHeaderContainer.getBottom()) / mHeaderHeight);mIsFinished = false;post(this);}}}//重写了ScrollView并且添加了滑动监听方法protected class InternalScrollView extends ScrollView {private OnScrollViewChangedListener onScrollViewChangedListener;public InternalScrollView(Context context) {this(context, null);}public InternalScrollView(Context context, AttributeSet attrs) {super(context, attrs);}public void setOnScrollViewChangedListener(OnScrollViewChangedListener onScrollViewChangedListener) {this.onScrollViewChangedListener = onScrollViewChangedListener;}@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);if (onScrollViewChangedListener != null) {onScrollViewChangedListener.onInternalScrollChanged(l, t, oldl, oldt);}}}//定义的滑动的回调接口private OnScrollViewChangedListener osc;public void setOnScrollViewChangedListener(OnScrollViewChangedListener osc){this.osc = osc;}public interface OnScrollViewChangedListener {public void onInternalScrollChanged(int left, int top, int oldLeft, int oldTop);public void OnScrollChangeListener(float f);}
}

主要结论

①根据传入的滑动距离/2的值加上原mHeaderContainer的高度设置到它的layoutparams中来达到放大的效果;

②视差效果是两个view同方向上移动速度的差别所产生的效果.

由于篇幅原因,这里只贴出对  PullToZoom ScrollViewEx 的主要注释,理解了PullToZoomScrollViewEx , PullToZoomListViewEx也是同理的。

想要看完整代码及demo,请链至github→         https://github.com/Frank-Zhu/PullZoomView

一探究竟之PullZoomView相关推荐

  1. 软件测试自学网站有哪些?不妨一探究竟

    一:前言 相信各位在学习培训的时候,无论学什么,都会习惯性地找自学网站.各位在自学软件测试的时候,也不会例外.那么,软件测试学习培训网站有哪些?我们不妨一探究竟. "我们需要去哪个网站学习培 ...

  2. android有多个活动,Android活动一探究竟

    作为Android的四大组件之一,活动最先走进我们的视野,其重要性不言而喻,今天就抽出时间来专门对Android活动一探究竟. 什么是活动 活动即Activity,是一种可以包含用户界面的组件,And ...

  3. 为何要配置环境变量?带你一探究竟

    一.前言 干了这么多年Java,配置环境变量都是第一步要做的,但是为什么要配置环境变量呢,又有什么用呢,今天哪吒就带你一探究竟. 二.百度百科 有事没事找百度,百度解释名词这一块做的是真的好. 1.环 ...

  4. 初识百态.末路归正:前方迷雾已散,待我一探究竟.《一》

    起源 { 本人从小非常喜欢计算机,由于我是98年的,刚好长大时计算机流行了起来,而且当时游戏正待巅峰时期.,列入[穿越火线,QQ飞车,等等....]随着对游戏的热爱, 我发现了外挂的存在.当时恍如发现 ...

  5. 平常人可以漂亮到什么程度?教你爬取知乎大神们的回答一探究竟!

    大家好,今天才哥带大家看看知乎这个高达14.3万关注,2.6亿浏览,回答数超过1.27万的问题<平常人可以漂亮到什么程度?>. 最近呢,可能是因为写了几篇关于爬虫获取美女照片的文章的缘故? ...

  6. 一探究竟:MyBatis的mapper查询接口返回的list会不会是null?

    文章目录 1 背景 2 debug一探究竟 2.1 mybatis-spring包中的SqlSessionTemplate 2.2 sqlSessionProxy对象 2.3 SqlSessionIn ...

  7. js函数形参、实参、arguments[]的一探究竟

    //先声明下:菜鸟原创,不当之处望大虾们指正, 函数的形参.实参.arguments[]都在哪个位置?参数通过值传递--传递流程是怎样的?js函数调用时接受的实参数目与函数定义时不一样行吗?--关于参 ...

  8. C语言中的signed和unsigned的使用以及整型提升一探究竟

    在C语言中,signed表示有符号的,是默认的,可以输出整数.负数 unsigned是无符号的,不能表示数值的正负 在整型提升的时候,如两个char型的字符相加(char型是一个字节),而计算机内部的 ...

  9. 可租赁、可定制的虚拟人居然还能这么玩?9月25日来百度大脑人像特效专场一探究竟!...

    百度大脑自2016年启动开放以来,已打造成为业内最全面.最领先的AI开放平台,服务规模.调用量都居于业界第一. 百度大脑开放日于2019年开办,覆盖北/上/深等地区,成为众多AI开发者.合作伙伴近距离 ...

最新文章

  1. 经常用得到的安卓数据库基类
  2. 推荐几首好听的Coldplay的歌
  3. centos7 安装 mysql5.7
  4. 聚簇索引与非聚簇索引学习总结
  5. JDK和cglib动态代理代码示例
  6. Boost::context模块fiber的分段的测试程序
  7. GitHub上的OpenJDK
  8. java决策树_【Java】决策树介绍和使用
  9. Oracle 获取每月最后一天的函数
  10. 2017-2018-1 20155338 加分项目——PWD的实现
  11. fcn+caffe+siftflow实验记录
  12. python安装pymssql等包时出现microsoft visual c++ 14.0 is required问题无需下载visualcppbuildtools的解决办法...
  13. 搜索引擎只能抓取html文件,为什么有些明明存在的网页不能被搜索到?
  14. uniapp前端处理接口返回一整个html格式
  15. 修改fstab导致UBUNTU无法启动的解决办法
  16. 印象笔记如何分享链接_印象笔记共享问题解决经过
  17. 常用的SQL注入语句
  18. 36.42. schemata
  19. win10任务栏假死原因和解决方法
  20. 人工智能时代党政人力资源的思考与变化

热门文章

  1. 5款靓汤 解决女人5个问题
  2. 在cygwin下如何转到D盘
  3. 二手房 房产 交易税 相关问题 总结
  4. 爱情是什么,怎样去诠释呢?
  5. 【论文笔记】—低照度图像增强—Supervised—GLADNet—2018-FG
  6. 图解KMP算法,带你彻底吃透KMP
  7. 【日常练习python】之MaShang
  8. Prometheus核心概念:你是如何在项目中使用Summary类型的Metric的?
  9. 森林救火模型matlab,数学建模1-森林救火模型
  10. 利用随机森林等模型进行企业纳税合规判断