利用ListView实现最简单的上滑悬停 (附源码)

来源:互联网 发布:水产加工erp软件 编辑:程序博客网 时间:2024/05/01 14:17

笔者之前在做一个项目的时候,需要实现一个特殊效果的布局,即列表顶部的布局能“下拉悬停”,也就是说,当列表往上滑动的时候,列表顶部的布局,能悬停在屏幕顶部,而不被滑出屏幕。

如图所示:当列表滑动时,搜索框也能随着滑动,然而搜索框无论如何却不会滑出布局之外,而是能悬停在顶部。
这里写图片描述

原理很简单,其实,搜索框布局一直都是随着列表滑动的,只是,当搜索框布局滑动到屏幕上边缘时,意味着该布局即将被挡住,这时候及时在布局顶部添加一个一模一样的布局(笔者称为替代布局),覆盖了原来的布局,所以,原先的搜索框布依旧就会被滑出布局外,然而由于替代布局至于布局顶部,所以会产生其该布局总是顶再屏幕上方的错觉。
笔者在下图中显示了布局边界,希望可以帮助各位更清晰地描述这一点。
这里写图片描述
这里写图片描述

所以这里就要解决一个问题:

如何知道搜索框布局在什么时候即将滑出屏幕边缘?

首次,笔者发现,在ListView,中,有个addHeaderView(View v) 方法,该方法可以让使用者添加任意特殊的View到ListView的顶部成为特殊的列表项,使得该特殊的View可以随着ListView的列表项一起滑动,并且可以添加多了列表项。笔者在demo中,就把搜索框布局通过该方法添加到ListView的顶部,使其成为ListView的列表项。这样做的好处是什么,答案在下面:
其次,我们可以为ListView注册滑动监听器:

ListView listView; //..... listView.setOnScrollListener(new AbsListView.OnScrollListener() {            @Override            public void onScrollStateChanged(AbsListView view, int scrollState) {            }            @Override            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {            }        });

这样就可以得到listView滑动时的一些信息了,比如:第二个回调函数onScroll(),只要ListView处于滑动状态,就会被不断调用,这个函数方有三个参数,重点是第二个参数:firstVisibleItem, 该参数记录了该ListView当前第一个可视的列表项是第几个,所以根据这个参数就得知某个列表项是否可视,所以搜索框布局成为ListView的列表项后,就能知道搜索框是否被屏幕顶部遮住,假设搜索框布局的位置为position,那么可能的情况是:
1.如果firstVisibleItem < position,,则搜索框布局没有被遮住;
2.如果firstVisibleItem >= position,,则搜索框布局即将或者已经被布局顶部遮住,这时就要把替代布局显示出来,
所以大概的实现原理就是这样:
让搜索框布局成为ListView的列表项,实时监测搜索框布局的位置,并在其将被遮挡的时候显示出替代布局。

下面看看代码吧:

首先看布局文件:
//first_layout.xml,用于显示顶部的图片而已

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical" android:layout_width="fill_parent"    android:gravity="center"    android:layout_height="128dp"><ImageView    android:layout_width="fill_parent"    android:layout_height="164dp"    android:scaleType="fitXY"    android:src="@drawable/pic"    /></LinearLayout>

//second_layout.xml,需要悬停在布局顶部的布局,很简单

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="horizontal" android:layout_width="match_parent"    android:layout_height="wrap_content"    android:id="@+id/secondLayout"    android:alpha="100"    android:padding="8dp"    android:background="#ccddcc"    >    <SearchView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="secondLaout"        android:textSize="48sp"        android:textColor="#ffffff"        android:layout_weight="1"        android:background="@android:color/white"        />    <Button        style="@style/Widget.AppCompat.Button.Borderless.Colored"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="搜索"        /></LinearLayout>

//activity_main.xml,主布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:orientation="vertical"    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">   <ListView       android:id="@+id/listView"       android:layout_width="fill_parent"       android:layout_height="fill_parent"/>    <!--隐藏在布局顶部的视图-->    <include layout="@layout/second_layout"        android:visibility="gone"        /></RelativeLayout>

布局文件都是很简单的,重点还是实现该功能的代码:
//MainActivity.java

public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener{    ListView listView;//滑动列表控件    List<Person> lit; //列表数据集,不是重点    DataAdapter adapter;//    /**     * 第一个headerView,显示顶部图片的视图     */    View firstLayout;    /**     * 第二个HeaderView,下拉时需要悬停在顶部的View     * 实际上该View在ListView向下滑动的那个时依旧会滑出界面,     * 此时,需要一个相同的view及时出现在屏幕顶部,替代之。     */    View secondLaout;    /**     * 替代布局:     *该View和secondLaout引用了同一个布局文件,所以二者会生成相同的view,     * hideView会隐藏在屏幕顶部,等到secondLaout正好触碰到屏幕顶部时,     * hideView就要及时出现,顶替secondLaout,以达到secondLaout好像悬停在屏幕顶部的效果。     */    View hideView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        firstLayout = LayoutInflater.from(this).inflate(R.layout.first_layout,null);        secondLaout = LayoutInflater.from(this).inflate(R.layout.second_layout, null);        hideView = findViewById(R.id.secondLayout);        lit = new ArrayList<>(30);        for(int i=0;i<30;i++)        {            lit.add(new Person("李白",25,"男"));        }        adapter = new DataAdapter(this,lit);        listView = (ListView)findViewById(R.id.listView);        //按顺序添加两个view,先添加的在上面。        listView.addHeaderView(firstLayout);        listView.addHeaderView(secondLaout);        listView.setAdapter(adapter);        //listView监听滑动事件,很重要,因为可以根据滑动的位置        //决定是否要加载隐藏的视图hideView        listView.setOnScrollListener(this);    }    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {    }    @Override    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {        /**         * 核心代码:         * 在滑动的那个过程中,判断如果第一个可见的列表项的位置(firstVisibleItem),         * 如果是firstVisibleItem大于等于第二个,也就是说:         * firstLayout已经滑出了屏幕,第一个可视的列表项是secondLaout或者是其后面的列表项,         * 即接下来会滑出或已经滑出屏幕的列表项是secondLaout,换句话说:         * secondLaout即将被挡住或者已经被挡住         * 此时要把hideView给显示出来,替代secondLaout.         */        if(firstVisibleItem >= 1)        {            hideView.setVisibility(View.VISIBLE);        }        else //说明secondLaout没有被屏幕挡住,那就把hideView给收起来。        {            hideView.setVisibility(View.INVISIBLE);        }    }}

代码及注释如上,是不是很简单呢,哈哈!

源码我放在github上了:
点我!

其实在尝试这个方案前,笔者和队友前前后后试了4,5中方案,效果都不尽人意,而这个点子,我也是了解到ListView.addHeaderView(View v)方法后才想到的,所以说,基础知识很重要啊!!功夫不负有心人。最后弄出来的时候,终于很安心地跑去睡觉了!!

PS,关于这个ListView.addHeaderView(View v)方法,根据其源码的注释,我发现了一些不同:以前addHeaderView(View v)方法要在setAdapter()方法调用前调用,否则会出错,但从Android 4.4开始,就没有这个限制了,这的确是个相当不错的改进啊!

PPS:笔者在实现这个功能后,才发现对于这种“顶部悬停”的效果,谷歌官方也是有做支持的!!详情请搜索Android Design Support Library这个强大的库。

2 0
原创粉丝点击