浅谈百度外卖筛选悬浮框效果的实现

来源:互联网 发布:淘宝创立于哪一年 编辑:程序博客网 时间:2024/06/10 08:08

     开始在项目中一直实现类似于百度外卖的上滑会使得筛选框的界面停留在顶部,下滑的时候就会随着ScrollView下滑动。这种效果也在网上百度了很多,但是会出现在滑动过快的时候出现抖动的效果,用户体验极差。后来在夏安明大神的博客上看到一个新的思路来实现这个效果,感觉这个方法实现的效果很流畅,并且个人觉得该方法很巧妙。夏安明大神开始也写过一篇android高仿美团网及大众点评悬浮框效果,第一篇看起来有点复杂,还用到Handler,每隔5s中发送一次信息更新滑动距离,下滑到某个位置的时候,将顶部的布局隐藏,上滑到某个位置将顶部布局显示。后来大神用了一个新的思路,再写了一篇修改版,瞬间感觉大神的思维就是不一样呀。好,那我们先来看下今天要实现的效果:

                                                                                                        

那就开始分析一下大神的思路吧:

        1、首先,在布局中必须要有两个相同的布局的悬浮框界面,一个在顶部悬浮,一个在正常的位置显示。

布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/parent_layout"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <include        android:id="@+id/include_header_home"        android:layout_width="match_parent"        android:layout_height="wrap_content"        layout="@layout/header_home" />    <com.mikyou.myview.MyScrollView        android:id="@+id/myscrollview"        android:layout_width="match_parent"        android:layout_height="match_parent" >        <FrameLayout            android:layout_width="match_parent"            android:layout_height="wrap_content" >            <LinearLayout                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:orientation="vertical" >                <ImageView                    android:layout_width="match_parent"                    android:layout_height="380dp"                    android:scaleType="fitXY"                    android:src="@drawable/center_image" />                <include                                       android:id="@+id/center_buy"                    layout="@layout/buy_layout" />                <ListView                    android:id="@+id/listview"                    android:layout_width="match_parent"                    android:layout_height="wrap_content"                    android:divider="#33000000"                    android:dividerHeight="0.1dp" >                </ListView>            </LinearLayout>            <include                android:id="@+id/top_buy"                layout="@layout/buy_layout" />        </FrameLayout>    </com.mikyou.myview.MyScrollView></LinearLayout>

布局显示的效果:

                                                                              

2、思路大致是这样的,在ScrollView滑动的过程中,去实时监听ScrollView的ScrollY值的变化,也就是上滑了多少距离。就是当中间的那个布局(centerBuy)还没有滑出ScrollView的边缘时,就利用layout方法重新绘制顶部布局(topBuy)的位置使得顶部布局的位置完全重合在中间布局的上面,使得看似出现只有一个这样的布局出现,实际上是两个相同的布局重叠在一起的效果,这是中间的布局还没有滑出ScrollView边缘的情况(就重叠在中间布局的上面);还有就是中间布局滑出了ScrollView的边缘时候,因为layout的方法绘制位置的时候坐标不是相对屏幕的而是相对父布局的,此时的父布局正好就是ScrollView,所以我顶部布局(topBuy)只要确定相对于距离ScrollView顶部即可,中间的布局(centerBuy)相对顶部的距离是定死的(centerBuy_getTop:在控制台打印出是:760),也就是说中间布局(CenterBuy)会随着ScrollView中的内容滑出去,所以在中间布局滑出ScrollView边缘的情况下,不断调整顶部布局绘制的位置即可也即是顶部布局距离ScrollView边缘的距离,因为滑出去多少也就是距离多少即为ScrollY的值。

总的来说两种情况:

                             1、中间布局没有滑出ScrollView边缘时,此时就需要绘制顶部的布局的位置使其重叠在中间布局上,随着一起上滑

                         

                             2、中间布局滑出了ScrollView边缘,而此时的顶部布局又需要一直悬浮在顶部位置,那么就需要在滑动过程中不断增大它与ScrollView的距离,该距离也就是ScrollY的值即上滑了多少距离。

 

分析图:

3、所以我们需要去实时获得ScrollY的值,其实这个值不是直接就能获取到的,因为在ScrollView中并没有直接提供相应的API给我们使用,有一个onScrollChanged方法可以得到滑动的距离,但是并没有直接暴露出来给我们使用,所以我们必须要去写一个回调的接口,在onScrollChanged方法中,调用接口中的回调方法,把滑动的距离的Y值传出来,即暴露出来供我们调用。

package com.mikyou.myview;import android.content.Context;import android.util.AttributeSet;import android.widget.ScrollView;public class MyScrollView extends ScrollView{private OnScrollViewListener onScrollViewListener;public MyScrollView(Context context) {super(context);}public MyScrollView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public MyScrollView(Context context, AttributeSet attrs) {super(context, attrs);}/** * @author zhongqihong * 定义ScrollView滚动时回调的接口 * onScroll方法用于返回的myScrollView滑动的Y方向的距离 * */public interface OnScrollViewListener{public void onScroll(int scrollY);}/** * @author zhongqihong * 设置滚动时候的接口 * */public void setOnScrollViewListener(OnScrollViewListener onScrollViewListener) {this.onScrollViewListener = onScrollViewListener;}@Overrideprotected int computeVerticalScrollRange() {return super.computeVerticalScrollRange();}/** * @author zhongqihong * 由于ScrollView中的方法onScrollchanged没有被暴露出去,所以不能直接调用它 * 所以就写了一个监听回调的接口,在该方法中调用,利用接口中的一个带参数的抽象方法,将 * onScrollchanged方法中变化的top值,也即是滑动过程中竖直方向上滑动过的距离即ScrollY * 传出去,即暴露出去供我们调用,从而可以动态地监听到滑动过程中ScrollY值的变化。 * */@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);if (onScrollViewListener!=null) {onScrollViewListener.onScroll(t);}}}


 

4、接着我们有了实时变化的ScrollY的值,就可以来实现我们的思路逻辑了:

package com.mikyou.scrollviewtest;import android.app.Activity;import android.os.Build;import android.os.Bundle;import android.view.View;import android.view.ViewGroup;import android.view.ViewGroup.LayoutParams;import android.view.ViewTreeObserver.OnGlobalLayoutListener;import android.view.Window;import android.view.WindowManager;import android.widget.BaseAdapter;import android.widget.LinearLayout;import android.widget.ListAdapter;import android.widget.ListView;import com.mikyou.myview.MyScrollView;import com.mikyou.myview.MyScrollView.OnScrollViewListener;import com.mikyou.tools.ScrollViewConflictWithListViewUtils;import com.mikyou.tools.SystemStatusManager;public class MainActivity extends Activity implements OnScrollViewListener{private ListView  mListView;private LinearLayout centerBuy,topBuy;private MyScrollView mScrollView;private ViewGroup mParentView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setTranslucentStatus();setContentView(R.layout.activity_main);initView();initData();}private void setTranslucentStatus() {//沉浸标题栏效果// TODO Auto-generated method stubif(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){Window win=getWindow();WindowManager.LayoutParams winParams=win.getAttributes();final int bits=WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;winParams.flags |=bits;win.setAttributes(winParams);}SystemStatusManager tintManager = new SystemStatusManager(this);tintManager.setStatusBarTintEnabled(true);tintManager.setStatusBarTintResource(0);tintManager.setNavigationBarTintEnabled(true);}private void initData() {initListViewData();}private void initListViewData() {mListView.setAdapter(new MyAdapter());/** * @author zhongqihong * 解决ScrollView与ListView冲突的问题 * 该方法需要在setAdapter之后调用 * */ScrollViewConflictWithListViewUtils.setListViewHeight(mListView);}private void initView() {registerAllViewId();registerAllViewEvent();}private void registerAllViewEvent() {mScrollView.setOnScrollViewListener(this);//但布局的状态以及布局中某些控件的可见性发生改变时,会触发回调mParentView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {//onScroll(mScrollView.getScrollY());//这一步很关键,因为在这里我们手动调用了onScroll方法//开始时初始化界面没滑动,mScrollView.getScrollY()为0,所以此时优先调用一次onScroll方法//如果我们不在初始化界面监听并手动调用该方法的话,onScroll方法,只会在滑动的时候产生回调//一开始并不会产生回调,也就会导致topBuy(顶部的布局)和centerBuy(中间的布局)无法在第一时间内整个界面一开始并没有滑动就重合//就会看见有两个相同的布局,一个在顶部,一个在中间左右,而我们一旦滑动就会触发回调onScroll方法,此时Math.max(scrollY,centerBuy.getTop)//取最大值肯定就是centerBuy.getTop(),那么接着利用layout方法,重新绘制topBuy(顶部布局),使得顶部布局正好与中间布局重合在一起,从而使得//所以,如果一开始我们就手动调用一次onScroll(mScrollView.getScrollY());就能在第一时间内使得两个布局重合,给人感觉就是一个布局,实际上是//两个布局,只是重新绘制了顶部布局位置,使它重叠在中间布局上面,给人感觉就是顶部布局消失了一样}});}private void registerAllViewId() {mListView=(ListView) findViewById(R.id.listview);centerBuy=(LinearLayout) findViewById(R.id.center_buy);topBuy=(LinearLayout) findViewById(R.id.top_buy);mScrollView=(MyScrollView) findViewById(R.id.myscrollview);mParentView=(ViewGroup) findViewById(R.id.parent_layout);}class MyAdapter extends BaseAdapter{@Overridepublic int getCount() {return 7;}@Overridepublic Object getItem(int position) {return getItem(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {View view=View.inflate(MainActivity.this, R.layout.home_list_item, null);return view;}}@Overridepublic void onScroll(int scrollY) {System.out.println("ScrollY---->"+scrollY);System.out.println("centerBuy_getTop中间布局离ScrollView距离:------->"+centerBuy.getTop());int topBuyParentTop=Math.max(scrollY, centerBuy.getTop());System.out.println("topBuy顶部布局离ScrollView顶部距离:"+topBuyParentTop);topBuy.layout(0, topBuyParentTop, topBuy.getWidth(), topBuyParentTop+topBuy.getHeight());}}


5、还有一点与夏大神不同的是顶部悬浮以后我下面用的是ListVIew,而大神用的则是多个ImageVIew撑起的ScrollView,所以我遇到一个问题就是ListVIew只显示一个Item,这就是我遇到的ScrollView与ListVIew嵌套冲突的问题。最后得出的原因是Scroll的事件消费以及ListVIew的高度的设置的问题,我在布局中是设置了warp_content的高度,不行,所以设置一个固定值后发现可以了,但是这个方法明显行不通,因为ListVIew的Item的个数是不定的,所以我必须要去测量出每一个item的高度以及中间分割线的高度的所有高度的总和最后才是ListVIew的高度。

package com.mikyou.tools;import android.view.View;import android.view.ViewGroup.LayoutParams;import android.widget.ListAdapter;import android.widget.ListView;public class ScrollViewConflictWithListViewUtils {/** * @author zhongqihong * 为了解决ScrollView和ListView的冲突 * 需要动态设置ListView的高度 * */public static void setListViewHeight(ListView lv){if (lv==null) {return ;}ListAdapter adapter=lv.getAdapter();//获得ListView的适配器对象if (adapter==null) {return ;}int totalHeight=0;//用于记录最后累加后的ListView的总高度。for (int i = 0; i < adapter.getCount(); i++) {//遍历整个适配器中的Item的ViewView listItem=adapter.getView(i, null, lv);listItem.measure(0, 0);//为每一个listItem测量高度totalHeight+=listItem.getMeasuredHeight();//累加每个已测量listItem的高度}LayoutParams params=lv.getLayoutParams();//先得到LayoutParams对象,接着就是修改该对象中的height的属性值params.height=totalHeight+(lv.getDividerHeight()*(lv.getCount()-1));//listItem的总高度+所有divider(分割线)的高度之和=最后的ListView的总高度lv.setLayoutParams(params);//最后重新设置LayoutParams对象}}


 

最后实现的效果:

点击下载源码

3 0
原创粉丝点击