android 仿QQ音乐歌单效果

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


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

android 仿QQ音乐歌单效果_第1张图片


而为了快速开发,不花时间在制造轮子上面,我选用是的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自己处理。

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

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


谢谢。


你可能感兴趣的:(android 仿QQ音乐歌单效果)