之前写的两篇关于自定义View:
http://www.jianshu.com/p/32d7d1ab985c 模仿饿了么加载效果(五八同城,UC也都有这个效果)

http://www.jianshu.com/p/e180aa9f293b 模仿小米的进度控件

先来看看效果图,这个gif弄得蛋疼,加快了播放速度,降低了清晰度:

scale_gif1.gif

GIF_20161019_230600.gif

github地址:https://github.com/niniloveyou/ScaleLayout

下面会从以下几个方面分析如何实现这个效果:

1.初始化完成后做了什么

2.onMeasure onLayout

3.触摸事件的处理

4.对外提供方法和接口

.
.
.
.
首先讲讲大概的思路:
就是我们要有三个View 分别为TopView CenterView bottomView 这很好理解,故名思义就是把这三个子View分别放在ViewGroup的上中下。
OnMeasure()中把CenterView的大小设置为等同于自身的大小
onLayout() 获取topview bottomView的高度,根据高度设置当centerView缩小时topView/BottomView位移距离
onInterceptTouchEvent() 只处理滑动冲突部分。
onTouchEvent()中才是真正滑动缩小或放大实现的部分。

1.初始化完成后做了什么

我们先贴代码,后面紧跟着解释:

    @Overrideprotected void onFinishInflate() {super.onFinishInflate();int childCount = getChildCount();if(childCount < 1){throw new IllegalStateException("ScaleLayout should have one direct child at least !");}mTopView = findViewById(R.id.scaleLayout_top);mBottomView = findViewById(R.id.scaleLayout_bottom);mCenterView = findViewById(R.id.scaleLayout_center);// if centerView does not exist// it make no senseif(mCenterView == null){throw new IllegalStateException("ScaleLayout should have one direct child at least !");}LayoutParams lp = (FrameLayout.LayoutParams)mCenterView.getLayoutParams();lp.gravity &= Gravity.CENTER;mCenterView.setLayoutParams(lp);//hide topView and bottomView//set the topView on the top of ScaleLayoutif(mTopView != null){lp = (FrameLayout.LayoutParams)mTopView.getLayoutParams();lp.gravity &= Gravity.TOP;mTopView.setLayoutParams(lp);mTopView.setAlpha(0);}//set the bottomView on the bottom of ScaleLayoutif(mBottomView != null){lp = (FrameLayout.LayoutParams)mBottomView.getLayoutParams();lp.gravity &= Gravity.BOTTOM;mBottomView.setLayoutParams(lp);mBottomView.setAlpha(0);}setState(mState, false);}

大家都知道onFinishInflate方法是View在XML中解析完成的回调,因此可以在里面做一些检查以及初始化的工作。 从代码不难看出,我首先就是检查了ScaleLayout的子View数量, 少于一个就直接抛出异常了,因为如果没有一个子View, 咱们自定义的这个ScaleLayout就没什么意义了, 其次 是我指定了 上中下三个子View的id, 这么做是因为ScaleLayout是个ViewGroup,可能不止三个,但是多了我们又没法判断,哪一个是topView, 哪个是centerView, 有可能会乱掉。

后面又对CenterView做了判空, 以及对三个View的位置做了些设置。

2.onMeasure onLayout

 /**
 * 使得centerView 大小等同ScaleLayout的大小* 如果不想这样处理,也可以在触摸事件中使用TouchDelegate* @param widthMeasureSpec* @param heightMeasureSpec*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);final int widthSize = MeasureSpec.getSize(widthMeasureSpec);final int heightSize = MeasureSpec.getSize(heightMeasureSpec);int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight();int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(layoutWidth, MeasureSpec.EXACTLY);int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(layoutHeight, MeasureSpec.EXACTLY);mCenterView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);if(mBottomView != null){mBottomViewMoveDistance = mBottomView.getMeasuredHeight();}if(mTopView != null){mTopViewMoveDistance = mTopView.getMeasuredHeight();}if(mSuggestScaleEnable){setMinScale(getSuggestScale());}
}

很简单,只说一点:mBottomViewMoveDistance, mTopViewMoveDistance 分别为bottomView, topView动画时位移的距离。

3.触摸事件的处理

重点来了这个也是核心部分了。

onInterceptTouchEvent

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {boolean intercept = false;switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:onTouchEvent(ev);mInitialMotionX = ev.getX();mInitialMotionY = ev.getY();break;case MotionEvent.ACTION_MOVE:final float deltaX = Math.abs(ev.getX() - mInitialMotionX);final float deltaY = Math.abs(ev.getY() - mInitialMotionY);if(mCanScaleListener != null&& !mCanScaleListener.onGetCanScale(ev.getX() - mInitialMotionX > 0)){intercept = false;}else {intercept = deltaY > deltaX && deltaY > mTouchSlop;}break;}return intercept;
}

所有的down事件都不拦截,因此接下来的move, up事件,
都会先执行onInterceptTouchEvent的(move, up)
继而分发给子view的dispatchTouchEvent(move, up),
因此在onInterceptTouchEvent(move)事件中我们可以判断是否满足滑动条件,满足就拦截,拦截了之后move up事件就会都分发给自身的OnTouchEvent, 否则如上继续分发给子View.

intercept = deltaY > deltaX && deltaY > mTouchSlop;

即Y位移的距离大于X方向 ,并且Y方向位移的距离大于TouchSlop,则认为这是有效滑动。

 /*** 返回是否可以scale,主要为了适配部分有滑动冲突的view* 如TouchImageView, 甚至webView等* isScrollSown = true  代表向下,* isScrollSown = false 代表向上*/public interface OnGetCanScaleListener{boolean onGetCanScale(boolean isScrollSown);}
if(mCanScaleListener != null && !mCanScaleListener.onGetCanScale(ev.getX() - mInitialMotionX > 0)){ intercept = false;
}

这下明白了吧,我是做了个接口,要不要拦截由你说了算,也算我偷懒了。

OnTouchEvent

 /*** 该方法中实现了* 上滑缩小下滑放大功能* 也可设置为 上滑放大下滑缩小* @param ev* @return*/@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (!isEnabled() || !mSlideScaleEnable) {return super.onTouchEvent(ev);}switch (ev.getActionMasked()) {case MotionEvent.ACTION_DOWN:downY = ev.getY();return true;case MotionEvent.ACTION_MOVE:if(mCanScaleListener != null && !mCanScaleListener.onGetCanScale(ev.getY() - downY > 0)){return super.onTouchEvent(ev);}if (Math.abs(ev.getY() - downY) > mTouchSlop) {mSlopLength += (ev.getY() - downY);float scale;if (mSlideUpOrDownEnable) {scale = 1 + (0.8f * mSlopLength / getMeasuredHeight());} else {scale = 1 - (0.8f * mSlopLength / getMeasuredHeight());}scale = Math.min(scale, 1f);mCurrentScale = Math.max(mMinScale, scale);doSetScale();downY = ev.getY();}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:if (mCurrentScale > mMinScale && mCurrentScale < 1f) {float half = (1 - mMinScale) / 2;if (mCurrentScale >= mMinScale + half) {setState(STATE_CLOSE, true);} else {setState(STATE_OPEN, true);}}break;}return super.onTouchEvent(ev);}

这部分,首先是move的时候用mSlopLength计算滑动的距离向下滑就加正值,向上划值就减小,不断根据这个值计算当前的Scale. 应该缩放的比例,然后根据这个值计算topView bottomView 的透明度,位移距离,等等, 当UP的时候,根据当前的Scale决定是应该放大到原宽高还是缩小,以动画的形式。

···
/**

 * 1.触发监听事件* 2.计算scale的pivotX, pivotY(因为topView 和bottomView 的高度可能不一样,所以不能固定设置在中心点)* 3.设置 mCenterView的scale* 4.设置topView and BottomView 的动画(渐变和位移)*/
private void doSetScale() {int scaleListenerCount = mScaleListenerList.size();OnScaleChangedListener mScaleChangedListener;for (int i = 0; i < scaleListenerCount; i++) {mScaleChangedListener = mScaleListenerList.get(i);if(mScaleChangedListener != null){mScaleChangedListener.onScaleChanged(mCurrentScale);}}if(mCurrentScale == mMinScale || mCurrentScale == 1f){int stateListenerCount = mStateListenerList.size();OnStateChangedListener mStateChangedListener;for (int i = 0; i < stateListenerCount; i++) {mStateChangedListener = mStateListenerList.get(i);if(mStateChangedListener != null){mStateChangedListener.onStateChanged(mCurrentScale == mMinScale);}}}doSetCenterView(mCurrentScale);doSetTopAndBottomView(mCurrentScale);
}

···

我把监听事件也贴上:

    /*** 当centerView 的scale变化的时候,通过这个* 接口外部的View可以做一些同步的事情,* 比如,你有一个其他的view要根据centerView的变化而变化*/public interface OnScaleChangedListener{void onScaleChanged(float currentScale);}/*** state == false 当完全关闭(scale == 1f)* state == true  或当完全开启的时候(scale = mMinScale)*/public interface OnStateChangedListener{void onStateChanged(boolean state);}

4.对外提供方法和接口

关于接口,代码我都无耻的贴上去了。

下面说说提供的几个简单的外部方法:

    /*** 设置最小scale* {@link #DEFAULT_MIN_SCALE}* @param minScale*/public void setMinScale(float minScale){if(minScale > 0f && minScale < 1f){if(mMinScale != minScale){if(isOpen()){if(animator != null){animator.cancel();animator = null;}animator = getAnimator(mMinScale, minScale);animator.start();}mMinScale = minScale;}}}public float getMinScale(){return mMinScale;}public float getCurrentScale(){return mCurrentScale;}public void setSuggestScaleEnable(boolean enable){if(mSuggestScaleEnable != enable){mSuggestScaleEnable = enable;requestLayout();}}/*** 设置的scale不得当的话,有可能topView / bottomView被覆盖* 通过设置{@link #setSuggestScaleEnable(boolean)}启用* @return*/private float getSuggestScale(){int height = 0;if(mTopView != null){height += mTopView.getMeasuredHeight();}if(mBottomView != null){height += mBottomView.getMeasuredHeight();}return 1 - height * 1f / (getMeasuredHeight() - getPaddingTop() - getPaddingBottom());}/*** 设置是否启用滑动缩小功能* @param enable*/public void setSlideScaleEnable(boolean enable){this.mSlideScaleEnable = enable;}/***   现在有这么几种情况, 默认第二种, 两者都可以的话,感觉好奇怪,*   比如一直下滑会由大变小后又变大,操作感觉不是很好*   1. 只上滑放大下滑缩小  false*   2. 只上滑缩小下滑放大  true*/public void setSlideUpOrDownEnable(boolean enable){this.mSlideUpOrDownEnable = enable;}/*** add OnScaleChangedListener* @param listener*/public void addOnScaleChangedListener(OnScaleChangedListener listener){if(listener != null){mScaleListenerList.add(listener);}}/*** add OnStateChangedListener* @param listener*/public void addOnStateChangedListener(OnStateChangedListener listener){if(listener != null){mStateListenerList.add(listener);}}public void setOnGetCanScaleListener(OnGetCanScaleListener listener){mCanScaleListener = listener;}/***  {@link #setState(int state, boolean animationEnable)}* @param state*/public void setState(int state){setState(state, true);}/*** 设置状态变化* @param state open or close* @param animationEnable change state with or without animation*/public void setState(final int state, boolean animationEnable) {if(!animationEnable){if(state == STATE_CLOSE){mSlopLength = 0;mCurrentScale = 1;}else{if(mSlideUpOrDownEnable) {mSlopLength = -getMeasuredHeight() * (1 - mMinScale) * 1.25f;}else{mSlopLength = getMeasuredHeight() * (1 - mMinScale) * 1.25f;}mCurrentScale = mMinScale;}doSetScale();mState = state;}else{if(animator != null){animator.cancel();animator = null;}if(state == STATE_CLOSE && mCurrentScale != 1){mSlopLength = 0;animator = getAnimator(mCurrentScale, 1f);}else if(state == STATE_OPEN && mCurrentScale != mMinScale){if(mSlideUpOrDownEnable) {mSlopLength = -getMeasuredHeight() * (1 - mMinScale) * 1.25f;}else{mSlopLength = getMeasuredHeight() * (1 - mMinScale) * 1.25f;}animator = getAnimator(mCurrentScale, mMinScale);}if(animator != null) {animator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {mState = state;}});animator.start();}}}/*** 获取当前状态开启或者关闭* @return*/public boolean isOpen(){return mState == STATE_OPEN;}

代码贴完了。

如果感觉还行,到我的github star一下吧。 谢谢!

https://github.com/niniloveyou/ScaleLayout

自定义ScaleLayout (模仿小米相册查看图片效果)(转载自作者 _deadline )相关推荐

  1. 模仿小米安全中心检测效果(进度条效果)

    模仿小米安全中心检测效果 废话少说,咱们先上效果图: github地址: https://github.com/niniloveyou/GradeProgressView 欢迎前去点个赞(star) ...

  2. 原生js实现查看图片效果

    网页查看图片原理:点击图片的时候创建一个div标签和一个img标签,将div标签通过appendChild方法加入body中,再将img标签加入div标签中.对html中的图片标签进行监听(并不是我们 ...

  3. 用户登录到相册查看图片(只是简单的图片展示,并没有实现相册和图片的管理,即删除和)

    主:项目地址:http://download.csdn.net/download/qq_28316183/9977095: 1.首先看数据表结构 user表: album表: picture表: 2. ...

  4. 高仿闪电报销app查看图片效果的实现

    2019独角兽企业重金招聘Python工程师标准>>> 图一为进入页面时展示的状态,图二,图三为向下拉content内容时图片放大显示 图四位当content内容区域拉动到一定范围时 ...

  5. android 照片点击查看,Android PhotoView点击放大图片效果

    使用的PhotoView是这个版本的,比较小巧,很好用,比github上另一个版本的瘦身很多:https://github.com/bm-x/PhotoView 基本测试代码如下:import jav ...

  6. 【React组件】写一个模仿蓝湖的图片查看器

    前言 最近公司让写一个可以自由拖拽放大的图片查看器,我寻思这还不简单,一顿操作猛如虎,俩小时后: 事实证明,一旦涉及到 DOM 的变换操作,如果很多细节考虑不全,抓过来就写,那基本就凉了.于是我仔细分 ...

  7. 仿小米相册列表实现自定义带快速索引功能的RecyclerView

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 篇章目标要点 一.实现效果 二.设计布局原理 三.关键代码实现 1.浮标随手势移动 2.浮标随列表移动 3.列表随浮标移动而 ...

  8. R语言可视化、编写自定义函数可视化水平排序条形图(horizontal bar plot)、自定义图像布局模仿经济学人杂志可视化效果、右侧添加标签数值图像方框、自定义背景色、水平条形图中间线条等

    R语言可视化.编写自定义函数可视化水平排序条形图(horizontal bar plot).自定义图像布局模仿经济学人杂志可视化效果.右侧添加标签数值图像方框.自定义背景色.水平条形图中间线条.网格线 ...

  9. css 实现 图片左右滑动查看的效果

    其实除了除了轮播图的那种图片效果外,图片滑动查看的效果 在网站中引用也是很多的,特别是在移动端,pc端的也有  就是我们是通过鼠标点击滑动的效果 我们先来看下效果吧 这样的效果 其实没必要用到js 直 ...

最新文章

  1. JAVA中String与StringBuffer的区别
  2. 蛋疼的ElasticSearch(一)之安装ElasticSearch
  3. 淘宝flink和storm书籍调研
  4. java多表查询返回数据_spring data jpa如何在多张数据库表中查询返回某些字段值?...
  5. Java最佳实践– Char到Byte和Byte到Char的转换
  6. java中j 和 j啥区别_从字节码层次分析++j和j++的区别
  7. ORACLE获取某个时间段之间的月份列表和日期列表
  8. CentOs7安装Oracle11g中的坑
  9. CS61A第一章笔记
  10. 友华PT921G光猫破解获取超级密码和更改桥接模式
  11. 年轻人的第一个开发板——树莓派
  12. SVM中对偶、凸优化与KTT条件问题
  13. win10新建菜单只有文件夹怎么办?
  14. 赵明magic4升鸿蒙,荣耀Magic新机生猛:折叠屏+骁龙888+鸿蒙系统,赵明:超越华为...
  15. 大数据归档-冷热数据分离
  16. 关于drawInRect: withAttributes: 等新方法的使用
  17. 计算机自带纸牌游戏卸载,如何彻底删除windows系统自带的游戏蜘蛛纸牌
  18. holder.js占位图片生成器
  19. 鼠友题库每日百题(一)
  20. SharePoint-Office365中修改顶部导航

热门文章

  1. java图书馆管理项目_java项目实战之图书馆管理系统(带源码和解析)
  2. MATLAB 安装通用摄像头插件
  3. 《小兵大战》运营日志
  4. 用计算机探求 已知,【计算机科学与探索杂志社】计算机科学与探索杂志社编辑部...
  5. 比亚迪DM-i双模驱动模式概述
  6. win10蓝牙功能不见了_「Windows」人走即锁屏,体验新功能“动态锁”,确实阔以...
  7. 单元化架构在金融行业的最佳实践
  8. python numpy报错:VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences
  9. MyBatis常见问题解答
  10. Vue - 列表分页懒加载 / 点击 “加载更多“ 按钮请求接口数据(如何实现类似用户手动点击 “查看更多“ ,然后请求分页懒加载数据填充)可适用于 Nuxt.js 、uni-app 等 Vue.js