最新的项目里面,有一个需求比较好玩,就是要仿造下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自己处理。
当然,在开发的时候可能部分机子会出现卡顿的情况,那么就需要改善了,需要改善的地方在哪里呢? 简单的说下能改善之处:
- 重新进行界面设计,剔除多Tab布局,优化交互
- 对布局代码进行优化,去除不必要的界面元素和ViewGroup
- 考虑是否放弃Fragment常驻内存的方案,不使用hide()和show()对Fragment进行控制,改用replace()等方案
谢谢。