ListView常用拓展

来源:互联网 发布:手机淘宝如何延长收货 编辑:程序博客网 时间:2024/06/03 15:47

        ListView虽然使用广泛,但系统原生的ListView显然是不能满足用户再审美、功能上不断提高需求。不过也不要紧,Android完全可以定制化,让我们非常方便的对原生ListView进行拓展、修改。于是,再开发者的创新下,ListView原来越丰富多彩,各种各样基于原生ListView的拓展让人目不暇接。下面来看几个常用的ListView拓展。

1具有弹性的ListView

        Android默认的ListView再滚动到顶端或低端的时候,并没有很友好的提示。再Android5.X中,Google为这样的行为添加了一个班月行的阴影效果。

        而在IOS系统中,列表都是具有弹性的,即滚动到低端或者顶端后会继续往下或者晚上滑动一段距离。不得不说,这样的设计的确更加的有友好。我们可以自己修改ListView,让ListView也可以“弹性十足”。

        网上有很多通过重写ListView来实现弹性效果的方法,比如增加HeaderView或者使用ScrollView进行嵌套,方法很多,不过这个可以使用一种非常简单的方法来实现这个效果。虽然不如那些方法可定制化高,效果丰富,但主要目的是让我们如何从源代码中找到问题的解决办法。

        再查看ListView源代码的时候可以发现,ListView中有个控制滑动到边缘的处理方法,如下所示。

protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)

      可以看见这样一个参数:maxOverScrollY,注释中这样写道----Number of pixels to overscroll by in either direction along the Y axis。由此可见,虽然它的默认值是0,但其实只要修改这个参数的值,就可以让ListView具有弹性了!所以,既然我们不知道为什么Google不采用这样的修改,那我们就自己来修改以下吧。重写这个方法,并将maxOverScrollY改为设置的值----mMaxOverDistance,代码如下所示。

    @Override    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxOverDistance, isTouchEvent);    }

        这样,通过修改这个值,就实现了具有弹性的ListView了。当然,为了能够满足多分辨率的需求,我们可以再修改maxOverScrollY值的时候,可以通过屏幕的density来计算具体的值,让不同分辨率的弹性距离基本一直。

    private void initView(){        DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();        float density = metrics.density;        mMaxOverDistance = (int) density * mMaxOverDistance;    }

2.自动显示、隐藏布局的ListView

        相信很多朋友都非常熟悉这样一个效果:当我们再ListView上滑动的时候顶部的ActionBar或ToolBar就会相应的隐藏或显示。
        下面我们就来仿照这样的例子设计一个类似的效果。

        我们知道,让一个布局显示或者隐藏并带有动画效果,可以通过属性动画很方便的实现,所以这个效果的关键在于如何获得ListView的各种滑动时间。所以借助View的onTouchListener接口来监听ListView的滑动,通过比较与上次坐标的大小,来判断滑动的方向,并通过滑动的方向来判断是否需要显示或者隐藏对应的布局。再开始判断滑动时间之前,我们还需要做一些准备工作,闪现需要给ListView增加一个HeaderView,避免第一个Item被Toolbar遮挡,代码如下所示。

    private View getHeadView(){        View header = new View(this);        header.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,                (int)getResources().getDimension(R.dimen.abc_action_bar_default_height_material)));        return header;    }

        再代码中,通过使用abc_action_bar_default_height_material属性获取系统ActionBar的高度,并设置给HeaderView。另外,定义一个mTouchSlop变量用来获取系统认为的最低滑动距离,即超过这个移动距离,系统就将其定义为滑动状态了。对于这个值的获取非常简单,代码如下所示。

mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();

        有了前面的准备工作,下面我们就可以判断滑动的事件了,关键代码如下所示。

    private float mFirstY;    private float mCurrentY;    private boolean mShow;    private ObjectAnimator mAnimtor;    private View.OnTouchListener touchListener = new View.OnTouchListener() {        @Override        public boolean onTouch(View v, MotionEvent event) {            switch (event.getAction()){                case MotionEvent.ACTION_DOWN:                    mFirstY = event.getY();                    break;                case MotionEvent.ACTION_MOVE:                    mCurrentY = event.getY();                    int direction = 0;                    if(mCurrentY - mFirstY > mTouchSlop){                        direction = 0;//down                    }else if(mFirstY - mCurrentY > mTouchSlop){                        direction = 1;//up                    }                    if(direction == 1){                        if(mShow){                            toolbarAnim(0);//hide                            mShow = ! mShow;                        }                    } else if(direction == 0) {                        if(!mShow){                            toolbarAnim(1);//show                            mShow = ! mShow;                        }                    }                    break;                case MotionEvent.ACTION_UP:            }            return false;        }    };

        代码逻辑非常简单,只是通过滑动的点坐标改变大小,来判断移动的方向,并根据移动方向来执行不同的动画效果。

        有了前面的分析,实现一个效果就非常简单了,最后加上控制布局隐藏的动画,如下所示。

    private void toolbarAnim(int flag) {        if(mAnimtor != null && mAnimtor.isRunning()){            mAnimtor.cancel();        }        if(flag == 0){            mAnimtor = ObjectAnimator.ofFloat(toolbar,                    "translationY",                    toolbar.getTranslationY(),                    0);        } else {            mAnimtor = ObjectAnimator.ofFloat(toolbar,                    "translationY",                    toolbar.getTranslationY(),                    -toolbar.getHeight());        }        mAnimtor.start();    }

        动画也是最简单的位移属性动画。不过这里需要说一点题外话,这里使用了Toolbar这一一个新空间,Google已经推荐它用来逐渐取代ActionBar了,因为它更加灵活。但是再使用的时候,一定要注意使用的theme一定要NoActionBar的,不然会引起冲突。


3.聊天ListView

        通常我们使用的ListView的每一项都具有相同的布局,所以展现出来的时候,除了数据不同,只要你不隐藏布局,其他的布局应该都是类似的。而我们熟知的qq等聊天APP,再聊天界面,会展示至少两种布局,即收到的消息和自己发送的消息,其实这一的效果也是通过ListView来实现的。

        这一一个ListView与我们平常所使用的ListView最大不同,就是它拥有两个不同的布局------收到的布局和发送的布局。要实现这一的效果,就需要拿List View的Adapter“开刀了”。

        再定义BaseAdapter的时候,需要去重写它的getView方法,这个方法就是用来获取布局的,那么只需要再获取布局的时候,判断下该获取哪一种布局就可以了。而且,ListView再设计的时候已经考虑到了这种清空,所以它提供了两个方法,代码如下所示。

    @Override    public int getItemViewType(int position) {        return super.getItemViewType(position);    }    @Override    public int getViewTypeCount() {        return super.getViewTypeCount();    }

        getItemViewType()方法用来返回第position个Item是何种类型的,而getViewTypeCount()方法用来返回不同布局的总数。通过这两个方法,再结合getView()方法,就可以很轻松的设计出上面的聊天布局了。 

        首先来实现两个布局------in和out。布局大小大同小异,只是方向上有区别。固只贴出了一个布局的代码。

<?xml version="1.0" encoding="utf-8"?><RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"    android:layout_height="match_parent"    android:layout_marginTop="20dp">    <ImageView        android:id="@+id/iv_in"        android:layout_marginLeft="20dp"        android:layout_marginRight="20dp"        android:layout_alignParentLeft="true"        android:layout_centerVertical="true"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <TextView        android:id="@+id/tv_in"        android:background="@drawable/in_background"        android:layout_toRightOf="@id/iv_in"        android:gravity="center"        android:layout_width="wrap_content"        android:layout_height="match_parent" /></RelativeLayout>

        同时为了封装下聊天的内容,便于再Adapter中获取数据信息,我们封装了一个Bean来保存聊天信息,代码如下所示。

public class ChatItemBean {    private int type;    private String info;    private Bitmap icon;    public ChatItemBean() {    }    public ChatItemBean(int type, String info, Bitmap icon) {        this.type = type;        this.info = info;        this.icon = icon;    }    public int getType() {        return type;    }    public String getInfo() {        return info;    }    public Bitmap getIcon() {        return icon;    }    public void setType(int type) {        this.type = type;    }    public void setInfo(String info) {        this.info = info;    }    public void setIcon(Bitmap icon) {        this.icon = icon;    }}

        代码非常简单,我们只是声明了需要的信息并提供了get()和set()方法。

        接下来,需要来完成最重要的BaseAdapter了,同样使用ViewHolder模式来提供ListView的效率,并再getView()方法中进行布局类型的判断,从而确定使用哪种布局。

import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.TextView;import java.util.List;/** * Created by 72312 on 2017/12/21. */public class MyAdapter extends BaseAdapter {    private List<ChatItemBean> mList;    private Context mContext;    public MyAdapter(List<ChatItemBean> mList, Context mContext) {        this.mList = mList;        this.mContext = mContext;    }    @Override    public int getItemViewType(int position) {        return mList.get(position).getType();    }    @Override    public int getViewTypeCount() {        return 2;    }    @Override    public int getCount() {        return mList.size();    }    @Override    public Object getItem(int position) {        return mList.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder viewHolder = null;        if(convertView == null) {            viewHolder = new ViewHolder();            if(getItemViewType(position) == 0){                convertView = LayoutInflater.from(mContext).inflate(R.layout.in,parent,false);                viewHolder.iv = convertView.findViewById(R.id.iv_in);                viewHolder.tv = convertView.findViewById(R.id.tv_in);            }else {                convertView = LayoutInflater.from(mContext).inflate(R.layout.out,parent,false);                viewHolder.iv = convertView.findViewById(R.id.iv_out);                viewHolder.tv = convertView.findViewById(R.id.tv_out);            }            convertView.setTag(viewHolder);        } else {            viewHolder = (ViewHolder)convertView.getTag();        }        viewHolder.iv.setImageBitmap(mList.get(position).getIcon());        viewHolder.tv.setText(mList.get(position).getInfo());        return convertView;    }    class ViewHolder {        ImageView iv;        TextView tv;    }}

        再以上的代码中,通过再getView中判断getItemViewType(position)的值来决定具体实例化哪个布局,从而实现再一个ListView中多个布局内容的添加。最后,再测试的Activity里面添加一些测试代码,来测试这个布局。
public class MainActivity extends AppCompatActivity {    private ListView list_view;    private List<ChatItemBean> mList;    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initDataListChat();        list_view = (ListView)findViewById(R.id.list_view);        MyAdapter adapter = new MyAdapter(mList,this);        list_view.setAdapter(adapter);    }    private void initDataListChat(){        mList = new ArrayList<>();        for (int i = 0; i < 30; i++) {            mList.add(new ChatItemBean(i%2, "hello " + i, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)));        }    }}


        


















原创粉丝点击