本文共 8934 字,大约阅读时间需要 29 分钟。
滑动冲突,这个是作安卓的必经之坑。最开始的ListView和ScollView冲突,或者ListView嵌套ListView滑动冲突,再或者ListView和ViewPager的滑动冲突,再或者是GraidView等可滑动控件互相嵌套的冲突。解决方案呢,有很多。比如在onTouchEvent中拦截事件。又或者自定义ListView,修改onMesure测量,使它在测量时获得最大的宽高,这样可以让它不滑动。全部展示,当然作为在android摸爬滚打了这么久的程序猿,这些坑都应该踩过了,而且网上一大堆解决方案,不得不说,这就是开源的好处啊,想着谷歌巴巴把kotlin扶上位了,我们这些苦逼的程序猿,那就只有跟着大部队走了。没办法呀~夹缝里生存。
View的绘制流程,Activity–phonewindow–decorview–contentview,如下图
我们平时在Activity的setContentView就是在ContentViews作文章。那么我们的冲突就是在这里,在ContentView里设置了一个activity_main.xml,为什么会有滑动冲突呢,那是因为recyclerview和scollview都设置在了activity_main.xml。那么换个角度,如果把recyclerview加在contentviews和activity_main.xml布局平级。那么是不是就不存在滑动冲突了呢,想到就来试试。
首先自定义一个view,用来弹窗。package com.example.administrator.bounceview;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.view.View;/** * Created by ShuWen on 2017/5/23. */public class BounceView extends View { private int mArcMaxHeight;//弹窗最高距离 private int mArcHeight;//记录变换过程的距离 private Paint mPaint;//画笔 private Path mPath = new Path();//绘制动画弧度 private BounceAnimatorListener animatorListener;//动画开始的监听回调 private Status status = Status.NONE;//记录动画的状态 public enum Status{ //没动,上升,下降 NONE,STATUS_UP,STATUS_DOWN } public BounceView(Context context) { super(context); init(); } public BounceView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public BounceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } //初始化 private void init() { mPaint = new Paint(); mPaint.setColor(Color.WHITE); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mArcMaxHeight = getResources().getDimensionPixelOffset(R.dimen.m_maxarcheight); } //上升的动画 public void show(){ status = Status.STATUS_UP; if (animatorListener != null){ this.postDelayed(new Runnable() { @Override public void run() { animatorListener.showContent(); } },600); } ValueAnimator animator = ValueAnimator.ofInt(0,mArcMaxHeight); animator.setDuration(700); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mArcHeight = (int) valueAnimator.getAnimatedValue(); if (mArcHeight == mArcMaxHeight){ bounce(); } invalidate(); } }); animator.start(); } //下降的动画 private void bounce() { status = Status.STATUS_DOWN; ValueAnimator valueAnimator = ValueAnimator.ofInt(mArcMaxHeight,0); valueAnimator.setDuration(600); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mArcHeight = (int) valueAnimator.getAnimatedValue(); invalidate(); } }); valueAnimator.start(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int currentY = 0; switch (status){ case NONE: currentY = 0; break; case STATUS_UP: currentY = (int) (getHeight()*(1 - (float)(mArcHeight/mArcMaxHeight))+mArcMaxHeight); break; case STATUS_DOWN: currentY = mArcMaxHeight; break; } mPath.reset(); mPath.moveTo(0,currentY); mPath.quadTo(getWidth()/2,currentY - mArcHeight,getWidth(),currentY); mPath.lineTo(getWidth(),getHeight()); mPath.lineTo(0,getHeight()); mPath.close(); canvas.drawPath(mPath,mPaint); } public void setAnimatorListener(BounceAnimatorListener animatorListener){ this.animatorListener = animatorListener; } public interface BounceAnimatorListener{ void showContent(); }}
上升过程中,绘制动画,使用ValueAnimator在回调里进行更新界面,调用invalidate()。中间使用到了二阶贝塞尔曲线,关于贝塞尔其实很简单的,在网上一搜,当然就有了。
那么在创建一个类,用来加载BounceVeiw。
package com.example.administrator.bounceview;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.view.ViewParent;import android.widget.FrameLayout;/** * Created by ShuWen on 2017/5/23. */public class BounceMenu { private RecyclerView recyclerView; private BounceView bounceView; private ViewGroup parentVG; private View rootView; private BounceMenu(View view, int resId, final MyAdapter myAdapter) { parentVG = findParentVG(view); rootView = LayoutInflater.from(view.getContext()).inflate(resId,null,false); recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview); bounceView = (BounceView) rootView.findViewById(R.id.bounceview); recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext())); bounceView.setAnimatorListener(new BounceView.BounceAnimatorListener() { @Override public void showContent() { recyclerView.setVisibility(View.VISIBLE); recyclerView.setAdapter(myAdapter); recyclerView.scheduleLayoutAnimation(); } }); } public static BounceMenu makeBounce(View view, int resId, final MyAdapter myAdapter){ return new BounceMenu(view, resId, myAdapter); } public void show(){ if (rootView != null){ parentVG.removeView(rootView); } ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); parentVG.addView(rootView,layoutParams); bounceView.show(); } private ViewGroup findParentVG(View view) { do { if (view instanceof FrameLayout){ //找到decorview的根布局 if (view.getId() == android.R.id.content){ return (ViewGroup) view; } } if (view != null){ ViewParent viewParent = view.getParent(); view = viewParent instanceof View? (View) viewParent :null; } }while (view!= null); return null; }}
在这里面,传入需要添加recylerview的跟布局,通过这个根布局获得decorview的contentviews这个布局,然后在这个布局上添加recyclerview。这样就是与activity_main同级,不会有滑动冲突。在bounceview的监听里,添加recyclerview的动画。
那么再来看看MianAcicity的代码:package com.example.administrator.bounceview;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.support.v7.widget.RecyclerView;import android.view.View;import android.widget.TextView;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity { private MyAdapter myAdapter; private ListstringList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); stringList = new ArrayList<>(); for (int i = 0; i < 20; i++) { stringList.add("阿西吧"+i); } myAdapter = new MyAdapter(this,stringList) { @Override protected int ItemLayoutId() { return R.layout.item; } @Override protected void onBindHolder(MyViewHolder myViewHolder, int position) { TextView textView = myViewHolder.getTextView(R.id.text); textView.setText(stringList.get(position)); } }; } public void click(View view){ BounceMenu bounceMenu = BounceMenu.makeBounce(findViewById(R.id.activity_main),R.layout.bounce_view_layout,myAdapter); bounceMenu.show(); }}
调用就是相当的简单了。同时还有炫酷的动画,何乐而不为呢。接下来所有代码都贴出来。
activity_main的xml布局。使用ScrollView,展示主要数据。在每一项的点击事件,触发弹窗,recyclerview。那么再看看弹窗的xml。
这里就是弹窗了,recyclerview和scrollview同级,不会产生滑动冲突。
这个是解决滑动冲突的一个可行方案,相当不错。如果觉得动画不必要,直接去掉动画,只需要BounceMenu中的一些逻辑就ok了。我会把代码放在git上,有兴趣的朋友可以自己研究研究。git地址:
效果图: