android 仿QQ音乐歌单效果

来源:互联网 发布:云计算简单解释 编辑:程序博客网 时间:2024/04/29 13:59

最新的项目里面,有一个需求比较好玩,就是要仿造下QQ音乐里面的歌单上下切换效果,如下


先做一个类似的效果,测试效果如下:



而为了快速开发,不花时间在制造轮子上面,我选用是的zhy大神的一个自定义layout,代码链接为 https://github.com/hongyangAndroid/Android-StickyNavLayout

当然,直接运用并不符合我们的实际开发需求效果,所以要在原来代码上加以修改。

直接上修改后的代码:

public class StickyNavLayout extends LinearLayout {private View mTop;private View mNav;private ViewPager mViewPager;private int mTopViewHeight;private ViewGroup mInnerScrollView;private boolean isTopHidden = false;private OverScroller mScroller;private VelocityTracker mVelocityTracker;private int mTouchSlop;private int mMaximumVelocity, mMinimumVelocity;private float mLastY;private boolean mDragging;private boolean isInControl = false;public StickyNavLayout(Context context, AttributeSet attrs) {super(context, attrs);setOrientation(LinearLayout.VERTICAL);mScroller = new OverScroller(context);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();mMaximumVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();mMinimumVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();}@Overrideprotected void onFinishInflate() {super.onFinishInflate();mTop = findViewById(R.id.id_stickynavlayout_topview);mNav = findViewById(R.id.id_stickynavlayout_indicator);View view = findViewById(R.id.id_stickynavlayout_viewpager);if (!(view instanceof ViewPager)) {throw new RuntimeException("id_stickynavlayout_viewpager show used by ViewPager !");}mViewPager = (ViewPager) view;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);ViewGroup.LayoutParams params = mViewPager.getLayoutParams();params.height = getMeasuredHeight() - mNav.getMeasuredHeight() - 140;//减相同高度防止listview高度超出显示范围}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mTopViewHeight = mTop.getMeasuredHeight() - 140;// 这里修改高度,为状态栏的高度// 具体可以通过分辨率来获取值丢进去,暂时为140}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {int action = ev.getAction();float y = ev.getY();switch (action) {case MotionEvent.ACTION_DOWN:mLastY = y;break;case MotionEvent.ACTION_MOVE:float dy = y - mLastY;getCurrentScrollView();if (mInnerScrollView instanceof ScrollView) {if (mInnerScrollView.getScrollY() == 0 && isTopHidden && dy > 0&& !isInControl) {isInControl = true;ev.setAction(MotionEvent.ACTION_CANCEL);MotionEvent ev2 = MotionEvent.obtain(ev);dispatchTouchEvent(ev);ev2.setAction(MotionEvent.ACTION_DOWN);return dispatchTouchEvent(ev2);}} else if (mInnerScrollView instanceof ListView) {ListView lv = (ListView) mInnerScrollView;View c = lv.getChildAt(0);if (!isInControl && c != null && c.getTop() == 0 && isTopHidden&& dy > 0) {isInControl = true;ev.setAction(MotionEvent.ACTION_CANCEL);MotionEvent ev2 = MotionEvent.obtain(ev);dispatchTouchEvent(ev);ev2.setAction(MotionEvent.ACTION_DOWN);return dispatchTouchEvent(ev2);}} else if (mInnerScrollView instanceof RecyclerView) {RecyclerView rv = (RecyclerView) mInnerScrollView;if (!isInControl&& android.support.v4.view.ViewCompat.canScrollVertically(rv, -1) && isTopHidden&& dy > 0) {isInControl = true;ev.setAction(MotionEvent.ACTION_CANCEL);MotionEvent ev2 = MotionEvent.obtain(ev);dispatchTouchEvent(ev);ev2.setAction(MotionEvent.ACTION_DOWN);return dispatchTouchEvent(ev2);}}break;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {final int action = ev.getAction();float y = ev.getY();switch (action) {case MotionEvent.ACTION_DOWN:mLastY = y;break;case MotionEvent.ACTION_MOVE:float dy = y - mLastY;getCurrentScrollView();if (Math.abs(dy) > mTouchSlop) {mDragging = true;if (mInnerScrollView instanceof ScrollView) {if (!isTopHidden|| (mInnerScrollView.getScrollY() == 0&& isTopHidden && dy > 0)) {initVelocityTrackerIfNotExists();mVelocityTracker.addMovement(ev);mLastY = y;return true;}} else if (mInnerScrollView instanceof ListView) {ListView lv = (ListView) mInnerScrollView;int position=lv.getFirstVisiblePosition();View c = lv.getChildAt(position);if (!isTopHidden || //(c != null //&& c.getTop() == 0//&& isTopHidden && dy > 0&&position==0)) {initVelocityTrackerIfNotExists();mVelocityTracker.addMovement(ev);mLastY = y;if(!isTopHidden&&position!=0){return false;}return true;}} else if (mInnerScrollView instanceof RecyclerView) {RecyclerView rv = (RecyclerView) mInnerScrollView;if (!isTopHidden|| (!android.support.v4.view.ViewCompat.canScrollVertically(rv, -1) && isTopHidden && dy > 0)) {initVelocityTrackerIfNotExists();mVelocityTracker.addMovement(ev);mLastY = y;return true;}}}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:mDragging = false;recycleVelocityTracker();break;}return super.onInterceptTouchEvent(ev);}private void getCurrentScrollView() {int currentItem = mViewPager.getCurrentItem();PagerAdapter a = mViewPager.getAdapter();if (a instanceof FragmentPagerAdapter) {FragmentPagerAdapter fadapter = (FragmentPagerAdapter) a;Fragment item = (Fragment) fadapter.instantiateItem(mViewPager,currentItem);mInnerScrollView = (ViewGroup) (item.getView().findViewById(R.id.id_stickynavlayout_innerscrollview));} else if (a instanceof FragmentStatePagerAdapter) {FragmentStatePagerAdapter fsAdapter = (FragmentStatePagerAdapter) a;Fragment item = (Fragment) fsAdapter.instantiateItem(mViewPager,currentItem);mInnerScrollView = (ViewGroup) (item.getView().findViewById(R.id.id_stickynavlayout_innerscrollview));}}@Overridepublic boolean onTouchEvent(MotionEvent event) {initVelocityTrackerIfNotExists();mVelocityTracker.addMovement(event);int action = event.getAction();float y = event.getY();switch (action) {case MotionEvent.ACTION_DOWN:if (!mScroller.isFinished())mScroller.abortAnimation();mLastY = y;return true;case MotionEvent.ACTION_MOVE:float dy = y - mLastY;// Log.e("TAG", "dy = " + dy + " , y = " + y + " , mLastY = " +// mLastY);if (!mDragging && Math.abs(dy) > mTouchSlop) {mDragging = true;}if (mDragging) {scrollBy(0, (int) -dy);if (getScrollY() == mTopViewHeight && dy < 0) {event.setAction(MotionEvent.ACTION_DOWN);dispatchTouchEvent(event);isInControl = false;}}mLastY = y;break;case MotionEvent.ACTION_CANCEL:mDragging = false;recycleVelocityTracker();if (!mScroller.isFinished()) {mScroller.abortAnimation();}break;case MotionEvent.ACTION_UP:mDragging = false;mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);int velocityY = (int) mVelocityTracker.getYVelocity();if (Math.abs(velocityY) > mMinimumVelocity) {fling(-velocityY);}recycleVelocityTracker();break;}return super.onTouchEvent(event);}public void fling(int velocityY) {mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, mTopViewHeight);invalidate();}@Overridepublic void scrollTo(int x, int y) {if (y < 0) {y = 0;}if (y > mTopViewHeight) {y = mTopViewHeight;}if (y != getScrollY()) {super.scrollTo(x, y);}isTopHidden = getScrollY() == mTopViewHeight;}@Overridepublic void computeScroll() {if (mScroller.computeScrollOffset()) {scrollTo(0, mScroller.getCurrY());invalidate();}}private void initVelocityTrackerIfNotExists() {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}}private void recycleVelocityTracker() {if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}}
布局文件为:

<RelativeLayout xmlns:tools="http://schemas.android.com/tools"    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <TextView        android:layout_width="match_parent"        android:layout_height="240dp"        android:gravity="top|center_horizontal"        android:text="模拟任务头像"        android:textSize="30sp"        android:textStyle="bold" />    <com.zhy.view.StickyNavLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:orientation="vertical" >        <RelativeLayout            android:id="@id/id_stickynavlayout_topview"            android:layout_width="match_parent"            android:layout_height="240dp"            android:background="@android:color/transparent" >        </RelativeLayout>        <com.zhy.view.SimpleViewPagerIndicator            android:id="@id/id_stickynavlayout_indicator"            android:layout_width="match_parent"            android:layout_height="50dp"            android:background="#ffffffff" >        </com.zhy.view.SimpleViewPagerIndicator>        <android.support.v4.view.ViewPager            android:id="@id/id_stickynavlayout_viewpager"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:background="#44ff0000" >        </android.support.v4.view.ViewPager>    </com.zhy.view.StickyNavLayout></RelativeLayout>


这里解释几点:

1.源代码上的效果只要是通过dispatchTouchEvent,,onInterceptTouchEvent,onTouchEvent来控制整个布局的触碰事件分发,其次是用过scrollby(distance)来移动布局从而达到

动画效果

2.之所以在onmeasure和OnsizeChanged方法里面减去高度是因为android版QQ歌单上始终会有歌手的头像在,因此不能简单的使用appbarLayout和CoordinatorLayout来实现,因其折叠时图片是处于隐藏状态且仅存状态栏的。

3.源代码不修改的话,会有一个bug,就是在viewpager中,数据高度不一致或者在其他listview滑至下方,然后左右切换,再回到原来的listview时,这是可以看到可见项不为0时,向下滑动会带动整个viewpager,而不是listview滑至第0项,因此要在onInterceptTouchEvent中添加这样一段代码:

if(!isTopHidden&&position!=0){return false;}
意思是如果当前没滑到最上,也就是viewpager和tablayout没到toolbar位置时,如果当前listview没到第0项,这时向下滑将不拦截该触碰事件,将其交给listview自己处理。

当然,在开发的时候可能部分机子会出现卡顿的情况,那么就需要改善了,需要改善的地方在哪里呢? 简单的说下能改善之处:

  • 重新进行界面设计,剔除多Tab布局,优化交互
  • 对布局代码进行优化,去除不必要的界面元素和ViewGroup
  • 考虑是否放弃Fragment常驻内存的方案,不使用hide()和show()对Fragment进行控制,改用replace()等方案
我们在上面的例子主要用到的是viewpager,这样会默认会至少有2个fragment在内存之中,所以在内存吃紧的机子那无可避免会出现上拉卡顿的情况,因此需要考虑到兼容的问题,还是有必要把viewpager干掉,用直接用fragment来直接使用切换相关的内容。


谢谢。


0 1
原创粉丝点击