阅读徐宜生《Android群英传》的笔记——第4章 ListView 使用技巧——剩下部分

4.1.8 处理空 ListView

ListView 用于展示列表数据,但当列表中无数据时,ListView 不会显示任何数据或提示,按照完善用户体验的需求,这里应该给以无数据的提示。幸好,ListView 提供了一个方法 —— setEmptyView(),通过这个方法我们可以给 ListView 设置一个在空数据下显示的默认提示。包含 ListView 的布局设置如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none" />

    <ImageView
        android:id="@+id/img_empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@mipmap/ic_launcher" />

</FrameLayout>

在代码中,我们通过以下方式给 ListView 设置空数据时要显示的布局,代码如下所示:

listView = (ListView) findViewById(R.id.list_view);
imgEmpty = (ImageView) findViewById(R.id.img_empty);
listView.setEmptyView(imgEmpty);

通过以上代码,就给 ListView 在空数据时显示一张默认的图片,用来提示用户;而在有数据时,则不会显示。MainActivity 的代码如下所示:

package com.example.test;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private ListView listView;
    private List<String> mData = new ArrayList<>();
    private ViewHolderAdapter adapter;
    private ImageView imgEmpty;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.list_view);
        imgEmpty = (ImageView) findViewById(R.id.img_empty);
//下面代码注释掉后表示没有数据
//        for (int i = 0; i < 30; i++) {
//            mData.add(i + "");
//        }
        adapter = new ViewHolderAdapter(this, mData);
        listView.setAdapter(adapter);
        listView.setEmptyView(imgEmpty);
    }

}

4.1.9 ListView 滑动监听

ListView 的滑动监听,是 ListView 中最重要的技巧,很多重写的 ListView,基本上都是在滑动事件的处理上下功夫,通过判断滑动事件进行不同的逻辑处理。而为了更加精确地监听滑动事件,开发者通常还需要使用 GestureDetector 手势识别、VelocityTracker 滑动速度检测等辅助类来完成更好的监听。这里介绍两种监听 ListView 滑动事件的方法,一个是通过 OnTouchListener 来实现监听,另一个是使用 OnScrollListener 来实现监听。

(1)OnTouchListener

OnTouchListener 是 View 中的监听事件,通过监听 ACTION_DOWN、ACTION_MOVE、ACTION_UP 这三个事件发生时的坐标,就可以根据坐标判断用户滑动的方向,并在不同的事件中进行相应的逻辑处理,这种方式的使用代码如下所示:

listView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        //触摸时操作
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //移动时操作
                        break;
                    case MotionEvent.ACTION_UP:
                        //离开时操作
                        break;
                }
                return false;
            }
        });

(2)OnScrollListener

OnScrollListener 是 AbsListView 中的监听事件,它封装了很多与 ListView 相关的信息,使用起来也更加灵活。首先来看一下 OnScrollListener 的一般使用方法,代码如下所示:

listView.setOnScrollListener(new AbsListView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        switch (scrollState) {
            case SCROLL_STATE_IDLE:
                //滑动停止时

                Log.i(TAG, "SCROLL_STATE_IDLE");
                break;
            case SCROLL_STATE_TOUCH_SCROLL:
                //正在滚动

                Log.i(TAG, "SCROLL_STATE_TOUCH_SCROLL");
                break;
            case SCROLL_STATE_FLING:
                //手指抛动时,即手指用力滑动,在离开后 ListView 由于惯性继续滑动

                Log.i(TAG, "SCROLL_STATE_FLING");
                break;
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        //滚动时一直调用

        Log.i(TAG, "onScroll");
    }
});

OnScrollListener 中有两个回调方法 —— OnScrollStateChanged() 和 onScroll()。
先来看第一个方法 OnScrollStateChanged(),这个方法根据它的参数 scrollState 来决定其回调的次数,scrollState 有以下三种模式:

  • SCROLL_STATE_IDEL:滚动停止时。
  • SCROLL_STATE_TOUCH_SCROLL:正在滚动时。
  • SCROLL_STATE_FLING:手指抛动时,即手指用力滑动,在离开后 ListView 由于惯性继续滑动的状态。

    当用户没有做手指抛动的状态时,这个方法只会回调 2 次,否则会回调 3 次,差别就是手指抛动的这个状态。通常情况下,我们会在这个方法中通过不同的状态来设置一些标志 Flag,来区分不同的滑动状态,供其他方法处理。
    下面再来看看 OnScroll() 这个回调方法,它在 ListView 滚动时会一直回调,而方法中的后三个 int 类型的参数,则非常精确地显示了当前 ListView 滚动的状态,这三个参数如下所示:

    • firstVisibleItem:当前能看见的第一个 Item 的 ID(从0开始)。
    • visibleItemCount:当前能看见的 Item 总数。
    • totalItemCount:整个 ListView 的 Item 总数。

      这里需要注意的是,当前能看见的 Item 数,包括没有显示完整的 Item,即显示一小半的 Item 也包括在内了。通过这几个参数,可以很方便地进行一些判断,比如判断是否滚动到最后一行,就可以使用如下代码进行判断,当前可视的第一个 Item 的 ID 加上当前可视 Item 的和等于 Item 总数的时候,即滚动到了最后一行。

if (firstVisibleItem + visivleItemCount == totalItemCount & totalItemCount >0){
    //滚动到最后一行
}

再比如,可以通过如下代码来判断滚动的方向,代码如下所示:

if (firstVisibleItem > lastVisibleItemPosition){
    //上滑
}else if (firstVisibleItem < lastVisibleItemPosition){
    //下滑
}
lastVisibleItemPosition = firstVisibleItem;

通过一个成员变量 lastVisibleItemPosition 来记录上次第一个可视的 Item 的 ID 并与当前的可视 Item 的 ID 进行比较,即可知道当前滚动的方向。
要理解整个 OnScrollListener,最好的方法还是在代码中添加 Log,并打印出状态信息来进行分析学习。在以上代码中,已经添加了相应的 Log,对照 Log 进行分析,会很快掌握 OnScrollListener 的用法。
当然,ListView 也给我们提供了一些封装的方法来获得当前可视的 Item 的位置等信息:

//获取可视区域内最后一个 Item 的 ID
listView.getLastVisiblePosition();
//获取可视区域内第一个 Item 的 ID
listView.getFirstVisiblePosition();

4.2 ListView 常用拓展

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

4.2.1 具有弹性的 ListView

Android 默认的 ListView 在滚动到顶端或者底端的时候,并没有很好的提示,在 Android 5.X 中,Google 为这样的行为只添加了一个半月形的阴影效果。而在 IOS 系统中,列表都是具有弹性的,即滚动到底端或者顶端后会继续往下或者往上滑动一段距离。不得不说,这样的设计的确更加的友好,虽然不知道 Google 为什么不模仿这样的设计,但我们可以自己修改 ListView,让ListView 也可以“弹性十足”。
这里可以使用一种非常简单的方法来实现这个效果,我们在查看 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 具有弹性了!重写这个方法,并将 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 dm = this.getResources().getDisplayMetrics();
    mMaxOverDistance = (int) (dm.density * mMaxOverDistance);
}

最后整理一下所有的代码就是下面这样:
自定义的 ListView:

package com.example.test;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.widget.ListView;

/**
* 自定义 ListView
* Created by HourGlassRemember on 2016/9/28.
*/
public class CustomListView extends ListView {

    private int mMaxOverDistance = 60;

    public CustomListView(Context context) {
        this(context, null);
    }

    public CustomListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    /**
    * 初始化

    */
    private void initView() {
        DisplayMetrics dm = this.getResources().getDisplayMetrics();
        mMaxOverDistance = (int) (dm.density * 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);
    }

}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.test.CustomListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none" />

</LinearLayout>

Adapter:

package com.example.test;

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 HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {

    private List<String> mData;
    private LayoutInflater mInflater;

    public ViewHolderAdapter(Context context, List<String> mData) {
        this.mData = mData;
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int position) {
        return mData.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        //判断是否缓存

        if (convertView == null) {
            holder = new ViewHolder();
            //通过 LayoutInflater 实例化布局

            convertView = mInflater.inflate(R.layout.viewholder_item, null);
            holder.imgIcon = (ImageView) convertView.findViewById(R.id.img_icon);
            holder.txtTitle = (TextView) convertView.findViewById(R.id.txt_title);
            convertView.setTag(holder);
        } else {
            //通过 tag 找到缓存的布局

            holder = (ViewHolder) convertView.getTag();
        }
        //设置布局中控件要显示的视图

        holder.imgIcon.setBackgroundResource(R.mipmap.ic_launcher);
        holder.txtTitle.setText(mData.get(position));
        return convertView;
    }

    public final class ViewHolder {
        public ImageView imgIcon;
        public TextView txtTitle;
    }

}

MainActivity 类:

package com.example.test;

import android.app.Activity;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";

    private CustomListView listView;
    private List<String> mData = new ArrayList<>();
    private ViewHolderAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (CustomListView) findViewById(R.id.list_view);
        for (int i = 0; i < 30; i++) {
            mData.add(i + "");
        }
        adapter = new ViewHolderAdapter(this, mData);
        listView.setAdapter(adapter);
    }

}

4.2.2 自动显示、隐藏布局的 ListView

相信用过 Google+ 的朋友应该非常熟悉这样一个效果:当我们在 ListView 上滑动的时候,顶部的 ActionBar 或者 Toolbar 就会相应的隐藏或显示。大家可以发现,在滚动前界面上加载了上方的标题栏和右下角的悬浮编辑按钮,当用户向下滚动时,标题栏和悬浮按钮消失,让用户有更大的空间去阅读。下面我们就来仿照这个例子设计一个类似的效果。代码如下所示:
布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#FF0000" />

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none" />

</LinearLayout>

MainActivity 类:

package com.example.test;

import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AbsListView;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";

    private ListView listView;
    private List<String> mData = new ArrayList<>();
    private ViewHolderAdapter adapter;
    //系统认为的最低滑动距离

    private float mTouchSlop;
    private float mFirstY, mCurrentY;
    //描述手指的滑动方向,direction为1表示向上,direction为0表示向下

    private int direction;
    private boolean mShow = true;
    private Toolbar mToolbar;
    private ObjectAnimator mAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mToolbar = (Toolbar) findViewById(R.id.toolbar);
        listView = (ListView) findViewById(R.id.list_view);

        //给 ListView 添加 HeaderView,避免第一个 Item 被 Toolbar 遮挡

        View headerView = new View(this);
        headerView.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
                (int) getResources().getDimension(R.dimen.abc_action_bar_default_height_material)));
        listView.addHeaderView(headerView);

        for (int i = 0; i < 30; i++) {
            mData.add(i + "");
        }
        adapter = new ViewHolderAdapter(this, mData);
        listView.setAdapter(adapter);

        //获得系统认为的最低滑动距离

        mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
        listView.setOnTouchListener(mTouchListener);
    }

    View.OnTouchListener mTouchListener = 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();
                    //计算手指是向上滑动还是向下滑动

                    direction = mCurrentY - mFirstY > mTouchSlop ? 0 : 1;
                    if (direction == 1 && mShow) {//向上滑动

                        toolbarAnim(1);//隐藏HeaderView
                    } else if (direction == 0 && !mShow) {//向下滑动

                        toolbarAnim(0);//显示HeaderView
                    }
                    mShow = !mShow;
                    break;
                case MotionEvent.ACTION_UP:
                    break;
            }
            return false;
        }
    };

    /**
    * 控制布局显示隐藏的动画

    *
    * @param flag
    */
    private void toolbarAnim(int flag) {
        //开始新动画之前要先取消掉之前的动画

        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.cancel();
        }
        mAnimator = ObjectAnimator.ofFloat(mToolbar, "translationY",
              mToolbar.getTranslationY(), flag == 0 ? 0 : -mToolbar.getHeight());
        mAnimator.start();
    }

}

4.2.3 聊天 ListView

通常我们使用的 ListView 的每一项都具有相同的布局,所以展现出来的时候,除了数据不同,只要你不隐藏布局,其他的布局应该都是类似的。而我们熟知的 QQ、微信等聊天 App,在聊天界面,会展示至少两种布局,即收到的消息和自己发送的消息,其实这样的效果也是通过 ListView 来实现的,下面我们就来模仿一个聊天软件的聊天列表界面,其效果如下图所示:
这里写图片描述
这样的一个 ListView 与我们平时所使用的 ListView 最大的不同,就是它拥有两个不同的布局 —— 收到的布局和发送的布局。要实现这样的效果,就需要拿 ListView 的Adapter“开刀”。具体代码如下所示:

发送方 Item 的布局(chat_item_send.xml):

<?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="wrap_content">

    <ImageView
        android:id="@+id/img_send"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:scaleType="centerCrop"
        android:src="@drawable/img2" />

    <TextView
        android:id="@+id/txt_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginRight="10dp"
        android:layout_toLeftOf="@+id/img_send"
        android:background="@drawable/chat_send"
        android:gravity="center"
        android:text="发送方" />

</RelativeLayout>

接收方 Item 的布局(chat_item_receive.xml):

<?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="wrap_content">

    <ImageView
        android:id="@+id/img_receive"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_centerVertical="true"
        android:scaleType="centerCrop"
        android:src="@drawable/img1" />

    <TextView
        android:id="@+id/txt_receive"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/img_receive"
        android:background="@drawable/chat_receive"
        android:gravity="center"
        android:text="接收方" />

</RelativeLayout>

ListView 的适配器(ViewHolderAdapter):

package com.example.test;

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 HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {

    private List<ChatItemBean> mData;
    private LayoutInflater mInflater;

    public ViewHolderAdapter(Context context, List<ChatItemBean> mData) {
        this.mData = mData;
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int position) {
        return mData.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    /**
    * 获得第 position 个 Item 是何种类型

    *
    * @param position
    * @return
    */
    @Override
    public int getItemViewType(int position) {
        return mData.get(position).getType();
    }

    /**
    * 返回不同类型的布局总数

    *
    * @return
    */
    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        //判断是否缓存

        if (convertView == null) {
            //根据 getItemViewType 的类型来判断是哪个布局

            if (getItemViewType(position) == 0) {//接收方——“对方”
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.chat_item_receive, parent, false);
                holder.imgHead = (ImageView) convertView.findViewById(R.id.img_receive);
                holder.txtContent = (TextView) convertView.findViewById(R.id.txt_receive);
            } else {//发送方——“我”
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.chat_item_send, parent, false);
                holder.imgHead = (ImageView) convertView.findViewById(R.id.img_send);
                holder.txtContent = (TextView) convertView.findViewById(R.id.txt_send);
            }
            convertView.setTag(holder);
        } else {
            //通过 tag 找到缓存的布局

            holder = (ViewHolder) convertView.getTag();
        }
        if (mData != null && mData.size() > 0) {
            holder.imgHead.setImageBitmap(mData.get(position).getHead());
            holder.txtContent.setText(mData.get(position).getContent());
        }
        return convertView;
    }

    public final class ViewHolder {
        public ImageView imgHead;
        public TextView txtContent;
    }

}

MainActivity 的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none" />

</LinearLayout>

MainActivity 类:

package com.example.test;

import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";

    private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.list_view);

        List<ChatItemBean> data = new ArrayList<>();
        data.add(addChatContent(1, "Hi,你是?!"));
        data.add(addChatContent(0, "Hello!"));
        data.add(addChatContent(1, "你好"));
        data.add(addChatContent(0, "在吗?"));
        data.add(addChatContent(1, "你是哪里的呢?"));
        data.add(addChatContent(0, "你猜"));
        listView.setAdapter(new ViewHolderAdapter(this, data));
        listView.setDivider(null);
    }

    /**
    * 添加聊天内容

    *
    * @param type
    * @param content
    */
    private ChatItemBean addChatContent(int type, String content) {
        ChatItemBean chatItemBean = new ChatItemBean();
        chatItemBean.setType(type);
        chatItemBean.setHead(BitmapFactory.decodeResource(getResources(),
                             type == 0 ? R.drawable.img1 : R.drawable.img2));
        chatItemBean.setContent(content);
        return chatItemBean;
    }

}

4.2.4 动态改变 ListView 布局

通常情况下,如果要动态地改变点击 Item 的布局来打到一个 Focus 的效果,一般有两种方法,一种是将两种布局写在一起,通过控制布局的显示、隐藏,来达到切换布局的效果;另一种则是在 getView() 的时候,通过判断来选择加载不同的布局。两种方法各有利弊,关键还是看使用的场合。下面就以第二种方法,来演示一下这样的效果,程序运行后初始效果如下所示,第一个 Item 默认 Focus 状态。当点击其他 Item 的时候,点击的 Item 变为 Focus 状态,其他 Item 还原。

程序初始状态
(程序初始状态)

Focus 改变
(Focus 改变)

具体实现的代码如下所示:
布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none" />

</LinearLayout>

ListView 的适配器(ViewHolderAdapter):

package com.example.test;

import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

/**
* Created by HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {

    private List<String> mData;
    private Context mContext;
    //当前 Item 的位置

    private int mCurrentItem;

    public ViewHolderAdapter(Context mContext, List<String> mData) {
        this.mContext = mContext;
        this.mData = mData;
    }

    public void setCurrentItem(int mCurrentItem) {
        this.mCurrentItem = mCurrentItem;
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object getItem(int position) {
        return mData.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    /**
    * 添加选中后 Item 的布局

    *
    * @param i
    * @return
    */
    private View addFocusView(int i) {
        LinearLayout layout = new LinearLayout(mContext);
        ImageView imageView = new ImageView(mContext);
        imageView.setImageResource(R.drawable.img1);
        layout.addView(imageView, new LinearLayout.LayoutParams(200, 200));
        layout.setGravity(Gravity.CENTER);
        return layout;
    }

    /**
    * 添加正常 Item 的布局

    *
    * @param i
    * @return
    */
    private View addNormalView(int i) {
        LinearLayout layout = new LinearLayout(mContext);
        layout.setOrientation(LinearLayout.HORIZONTAL);
        ImageView imageView = new ImageView(mContext);
        imageView.setImageResource(R.drawable.img3);
        layout.addView(imageView, new LinearLayout.LayoutParams(200, 200));
        TextView textView = new TextView(mContext);
        textView.setText(mData.get(i));
        layout.addView(textView, new LinearLayout.LayoutParams(200, 200));
        return layout;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LinearLayout layout = new LinearLayout(mContext);
        layout.setOrientation(LinearLayout.VERTICAL);
        //通过判断当前 CurrentItem 是否是那个点击的 position,就可以动态控制显示的布局了

        layout.addView(mCurrentItem == position ? addFocusView(position) : addNormalView(position));
        return layout;
    }

}

MainActivity 类:

package com.example.test;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";

    private ListView listView;
    private ViewHolderAdapter adapter;
    private List<String> mData = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.list_view);

        for (int i = 0; i < 20; i++) {
            mData.add(i + "");
        }
        adapter = new ViewHolderAdapter(this, mData);
        listView.setAdapter(adapter);
        //重写 ListView 中 Item 点击的监听事件,记录当前点击的 Item 的位置,
        //并让 ListView 刷新一次以便更新界面的内容

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                adapter.setCurrentItem(position);
                adapter.notifyDataSetChanged();
            }
        });
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值