前一段时间阳光小强安装了一个豆瓣客户端,第一次打开就被这种界面风格吸引了,今天早上起来在打开豆瓣听音乐的时候,突然产生一个念头,来试着实现一下这种效果,打开客户端分析了一下发现其实这种效果的实现并不是想象中的那么难,下面我先分析一下这种效果的实现思路,然后一步步解释实现的过程,希望大家能提出意见和建议,一起交流学习。
先给大家展示一下我的成果吧:
其实豆瓣客户端的界面上还有其他的文字和菜单,但是这两个的实现效果和其他几个类似,可以作为代表,所以就不绘制那么多组件了。
转载请说明出处:http://blog.csdn.net/dawanganban
一、分析界面的组成结构
有两个和我们手机屏幕尺寸大小相等的View(分别是灰色透明度变化的背景和主界面),假设屏幕的宽和高是w和h, 屏幕的坐标原点在左上角,这两个View相对于屏幕的坐标是
刚开始的坐标:
灰色背景: left 0 right w top -h bottom 0
主界面:left 0 right w top 0 bottom h
滑动到最底部(d为滑动到最底部的高度)
灰色背景: left 0 right w top -d bottom h-d
主界面: left 0 right w top h-d bottom 2h-d
接下来我们就要分析两个大问题:
1、滑动的具体实现
从上面的图上可以很容易的看出来,此时我们就要考虑如何实现界面上下滑动,观察豆瓣客户端的滑动手势发现支持滑动加速度检测(速度大于某值时直接从一端滑向另一端)、支持屏幕跟随手指滑动、屏幕的Y轴方向的中间是一个恢复位置的临界点。
从上面的分析我们基本上可以知道用到的技术有如下几个:
(1)监听Event_Move事件,通过scorllBy实现实时移动(跟随手指)。
(2)判断Event_Up时的手指位置,来判断是否恢复到原来位置。
(3)滑动的时候判断手指滑动的速度,来确定是否直接滑动到另一端(上端和下端)。
2、界面上元素的缩放和透明度的变化
界面上透明度变化的地方大致有这几处,滑动时上面的灰色背景透明度渐变(从上向下滑动变透明,从下向上滑动变灰),主界面上的文字透明度变化。
缩放的控件有主界面上的圆形图片和底部菜单(底部菜单是类似的实现,这里不做讨论),而且随着滑动位置从水平居中向左边移动并且变小。
透明度变化的实现其实很简单,只需要知道当前位置相对整个屏幕坐标的比例计算出来即可,现在比较难的是如何实现中间圆形图片的缩放和位置的移动。
可以简单的从上图中看出大概的变化规律,我们要参考的时屏幕的TOP和LEFT来确定圆形的位置。
二、实现过程详解
首先我们添加两个View(灰色界面和主界面)
private void addChild(){ addTopView(); addCenterView(); }
private void addTopView(){ View view = new View(context); view.setBackgroundColor(Color.GRAY); mTopView = view; addView(mTopView); } private void addCenterView(){ View view = new CustomCenterVIew(context); view.setBackgroundColor(Color.WHITE); mCenterView = view; addView(mCenterView); }重写ViewGroup,然后再onLayout中对两个View进行布局(初始布局)
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mTopView.layout(0, -mViewHeight, mViewWidth, 0); mCenterView.layout(0, 0, mViewWidth, mViewHeight); }我们重点来看一下对屏幕事件的处理,下面是重写onTouchEvent方法
private float mOldY; private VelocityTracker vTracker; @Override public boolean onTouchEvent(MotionEvent event) { int disY; int vTrackY; float eventY = event.getY(); obtainVelocityTracker(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mOldY = eventY; break; case MotionEvent.ACTION_MOVE: disY = (int)(eventY - mOldY); if(disY > 0 && Util.getCenterViewPointY(mCenterView) >= mViewHeight){ return true; } if(disY < 0 && Util.getCenterViewPointY(mCenterView) <= mViewPointY){ return true; } mOldY = eventY; scrollBy(0, -disY); break; case MotionEvent.ACTION_UP: vTracker.computeCurrentVelocity(1000); vTrackY = (int) vTracker.getYVelocity(); Log.e(TAG, "vTrack = " + vTrackY); if(Math.abs(vTrackY) > 6000){ if(vTrackY > 0){ moveToBottom(); }else{ moveToTop(); } }else{ int disPointY = Util.getCenterViewPointY(mCenterView) - mViewPointY; if(disPointY > mViewHeight / 2){ moveToBottom(); }else{ moveToTop(); } } break; } return true; }在ACTION_MOVE事件中我们做了两个条件判断是为了防止滑动到最上边后或滑动到最下端还可以继续滑动(限定了一个滑动的范围),然后使用scrollBy滑动响应(手机滑动)的距离。
在ACTION_UP中主要做了两件事,一个是判断用户的手指滑动速度是否超出了一个临界值,如果超出则表明用户是想滑动到底的。另一个是判断是否手指滑动到了屏幕的中界线以外来处理滑动到底还是恢复到原来的位置。
为了实现平滑的滑动我们在moveToBottom和moveToTop中使用了computerScroll方法来实现平滑滑动效果。关于详细用法请参考我的另一篇博文:http://blog.csdn.net/dawanganban/article/details/23998781
接下来我们来看一下主界面View的实现,这个View其实是一个自定义View,我重写了onDraw方法来实现透明度和大小的变化(使用系统动画实现不了随着手指移动实时变化的效果)具体的实现如下:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int currentNum = Util.getCenterViewPointY(this); Log.e(TAG, "currentNum = " + currentNum); int alpha = 255 - (int)((float)500 * (currentNum - CustomView.BOTTOM_MENU_HEIGHT) / maxNum); if(alpha > 0){ titleTextPaint.setAlpha(alpha); float titleWidth = titleTextPaint.measureText(title); canvas.drawText(title, (mViewWidth - titleWidth) / 2, TITLE_TOP_MARGIN, titleTextPaint); } int beginX = (int)((mViewWidth - mCenterIconBitmap.getWidth()) / 2 - (float)CENTER_ICON_LEFT_GAP * (currentNum - CustomView.BOTTOM_MENU_HEIGHT) / maxNum); int beginY = (int)(CENTER_ICON_MARGIN - (float)CENTER_ICON_MARGIN * currentNum / maxNum); canvas.save(); float scale = 1.6f - (float)currentNum / maxNum; canvas.scale(scale, scale, beginX + mCenterIconBitmap.getWidth() / 2, beginY + mCenterIconBitmap.getHeight() / 2); rectf.set(beginX, beginY, beginX + mCenterIconBitmap.getWidth(), beginY + mCenterIconBitmap.getHeight()); canvas.drawBitmap(mCenterIconBitmap, null, rectf, bitmapPaint); canvas.restore(); }在上面的绘制中主要是计算大小和透明度来实现绘制中间圆形和文字的效果,现在我们运行的时候可以发现并不是我们所想的可以实现我们想要的效果,这个onDraw方法根本不会随着滑动调用,那么怎么办呢?我们可以让滑动的时候来通知自定义View的重绘(这里可以做一些优化)。
@Override public void computeScroll() { super.computeScroll(); if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } centerViewUpdate(); }看以看到在computeScroll的最后一行我们调用了一个centerViewUpdate方法来实现让自定义View和上面灰色界面透明度变化的重绘,代码如下:
private void centerViewUpdate(){ mCenterView.postInvalidate(); float alpha = 1 - (float)(Util.getCenterViewPointY(mCenterView) - mViewPointY) / mViewHeight; if(alpha < 0){ mTopView.setVisibility(View.GONE); }else{ mTopView.setVisibility(View.VISIBLE); mTopView.setAlpha(alpha); mTopView.postInvalidate(); } }至此整个工作基本完成,完整源代码请狂点右下角跳舞的小人,加群后在群共享中获取,或者点此下载