最新的项目里面,有一个需求比较好玩,就是要仿造下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(); } @Override protected 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; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); ViewGroup.LayoutParams params = mViewPager.getLayoutParams(); params.height = getMeasuredHeight() - mNav.getMeasuredHeight() - 140;//减相同高度防止listview高度超出显示范围 } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mTopViewHeight = mTop.getMeasuredHeight() - 140;// 这里修改高度,为状态栏的高度 // 具体可以通过分辨率来获取值丢进去,暂时为140 } @Override public 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); } @Override public 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)); } } @Override public 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(); } @Override public 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; } @Override public 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自己处理。
当然,在开发的时候可能部分机子会出现卡顿的情况,那么就需要改善了,需要改善的地方在哪里呢? 简单的说下能改善之处:
谢谢。