Android adapter ListView

一、适配器模式

参考
Android中Adapter的学习与思考
Android源码之ListView的适配器模式

我们知道Adapter就是适配器的意思。在GOF设计模式中存在一种设计模式,即是适配器模式(Adapter)。对设计模式的学习使我们知道:适配器模式能够将一个接口转换为客户所期望的另一个接口,使得原来由与接口不兼容而不能一切工作的类可以一起工作。举个简单例子:大家都知道笔记本的电源插头一般是三孔的,假定你家里没有三孔的插座,而只有两孔的怎么办。解决方法很简单,就是去买一个带三孔和两孔的插板,并且插板的插头应该是两孔的。这样问题就解决了嘛。这种解决的方法就是一种适配器模式,而插板就是适配器。

作为最重要的View,ListView需要能够显示各式各样的视图,每个人需要的显示效果各不相同,显示的数据类型、数量等也千变万化。那么如何隔离这种变化尤为重要。Android的做法是增加一个Adapter层来应对变化,将ListView需要的接口抽象到Adapter对象中,这样只要用户实现了Adapter的接口,ListView就可以按照用户设定的显示效果、数量、数据来显示特定的Item View。
通过代理数据集来告知ListView数据的个数( getCount函数 )以及每个数据的类型( getItem函数 ),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出为View ( getView函数 ),这样就很好的应对了Item View的可变性。简单的说Adapter就是AdapterView视图与数据之间的桥梁,Adapter提供对数据项的访问,同时也负责为每一项数据产生一个View.

Paste_Image.png
Paste_Image.png

由上述适配图就可以看出其实Android中的Adapter与设计模式中的Adapter特点都是一样的,虽然ListView需要的数据接口与Data Source并不兼容,但是通过Adater却可以让ListView使用Data Source。这与java中适配器模式的理念不谋而合!

二、ArrayAdapter

Android ArrayAdapter 详解
使用详解及源码解析Android中的Adapter、BaseAdapter、ArrayAdapter、SimpleAdapter和SimpleCursorAdapter
1.简单显示文本

//activity_main.xml:
<LinearLayout...>
   <ListView
       android:id="@+id/list_view"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
   </ListView>
</LinearLayout>

//MainActivity.java
public class MainActivity extends Activity{
    private String[] data = {"Apple","Banana","Orange"};
    
    protected void onCreate(Bundle savedInstanceState){
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       
       //ArrayAdapter可以通过泛型来指定适配的数据类型,本例数据为String
       //simple_list_item_1是android内置布局文件,里面只有一个textview
       ArrayAdapter<String> adapter = new ArrayAdapter<String>(
       MainActivity.this,android.R.layout.simple_list_item_1,data);
       ListView listview = (ListView) findViewById(R.id.list_view);
       listview.setAdapter(adapter);
    }
}

2.简单显示文本,另外同时显示对应图片

//新建一个实体类Fruit
public class Fruit{
   private String name;
   private int imageId;
   
   public Fruit(String name, int imageId){
      this.name = name;
      this.imageId = imageId;
   }
   
   public String getName(){
       return name;
   }
   
   public int getImageId(){
       return imageId;
   }
}

//layout目录下新建fruit_item.xml
<LinearLayout...>
   <ImageView fruit_image>
   <TextView fruit_name>
</LinearLayout>


//自定义适配器
public class FruitAdapter extends ArrayAdapter<Fruit>{
   private int resourceId;
   //构造方法中指定了泛型Fruit
   public FruitAdapter(Context context,
   int textViewResourceId, List<Fruit> objects){
      super(context, textViewResourceId, objects);
      resourceId = textViewResourceId;
   }

   //getView方法在每个子项被滚动到屏幕内的时候调用
   @override
   public View getView(int postion, View convertView, ViewGroup parent){
      Fruit fruit = getItem(postion);
      //加载布局
      View view = LayoutInflater.from(getContext()).inflate(resourceId,null);
      ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
      TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
      fruitImage.setImageResource(fruit.getImageId());
      fruitName.setText(fruit.getName());
      //返回布局
      return view;
   }
}

//MainActivity.java

private List<Fruit> fruitList = new ArrayList<Fruit>();
private void initFruits(){
   Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
   fruitList.add(apple);
   ...
}

protected void onCreate(){
   ...
   FruitAdapter adapter = new FruitAdapter
   (MainActivity.this,R.layout.fruit_item,fruitList);
   ...
   listview.setAdapter(adapter);
}

3.优化getView
getView每次都把布局重新加载了一遍,观察一下getView(int postion, View convertView, ViewGroup parent),contentView参数用于将之前加载好的布局进行缓存,以便之后可以重用。所以可以这样:

View view;
if(converView == null){
   view = LayoutInflater.from(getContext()).inflate(resourceId,null);
}else{
   view = convertView;//直接对convertView进行重用
}

不过我们可以继续优化,把那些findViewById也给缓存起来

View view;
ViewHolder viewHolder;
if(converView == null){
   view = LayoutInflater.from(getContext()).inflate(resourceId,null);
   viewHolder = new ViewHolder();
   viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
   viewHolder.fruitName = (TextView) view.findViewById(R.id.fruit_name);
   view.setTag(viewHolder);//缓存起来
}else{
   view = convertView;//直接对convertView进行重用
   viewHolder = (ViewHolder) view.getTag();
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());
return view;

class ViewHolder{
   ImageView fruitImage;
   TextView fruitName;
}

4.点击事件

listView.setOnItemClickListener(new OnItemClickListener(){
   @override
   public void onItemClick(AdapterView<?> parent,
   View view,int position, longid){
      Fruit fruit = fruitList.get(position);
      Toast.makeText(MainActivity.this,fruit.getName(),
      Toast.LENGTH_SHORT).show();
   }
});
三、SimpleAdaper

参考
使用详解及源码解析Android中的Adapter、BaseAdapter、ArrayAdapter、SimpleAdapter和SimpleCursorAdapter

SimpleAdaper的作用是方便地将数据与XML文件定义的各种View绑定起来,从而创建复杂的UI。SimpleAdapter有很强的扩展性,可以自定义出各种效果。
<pre>
package com.ispring.adapter;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.SimpleAdapter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView)findViewById(R.id.listView);
final String[] names = {"Windows","Mac OS","Linux","Android","Chrome OS"};
final String[] descriptions = {
"Windows是微软公司的操作系统",
"Mac OS是苹果公司的操作系统",
"Linux是开源免费操作系统",
"Android是Google公司的智能手机操作系统",
"Chrome OS是Google公司的Web操作系统"
};
final int[] icons = {
R.drawable.windows,
R.drawable.mac,
R.drawable.linux,
R.drawable.android,
R.drawable.chrome
};
List<Map<String, Object>> list = new ArrayList<>();
for(int i = 0; i < names.length; i++){
HashMap<String, Object> map = new HashMap<>();
map.put("name", names[i]);
map.put("description", descriptions[i]);
map.put("icon", icons[i]);
list.add(map);
}
//每个数据项对应一个Map,from表示的是Map中key的数组
String[] from = {"name", "description", "icon"};
//数据项Map中的每个key都在layout中有对应的View,
//to表示数据项对应的View的ID数组
int[] to = {R.id.name, R.id.description, R.id.icon};
//R.layout.item表示数据项UI所对应的layout文件
SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.item, from, to);
listView.setAdapter(adapter);
}
}
</pre>

其中R.layout.item表示数据项UI所对应的layout文件,如下所示:
<pre>
<?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="wrap_content"
android:orientation="horizontal"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<ImageView android:id="@+id/icon"
android:layout_width="100dp"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="10dp">
<TextView android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/defaultFontSize" />
<TextView android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/defaultFontSize"
android:layout_marginTop="10dp"/>
</LinearLayout>
</LinearLayout>
</pre>

SimpleAdapter只有一个构造函数,签名如下所示:
public SimpleAdapter (Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)

  • data表示的是List数据源,其中List中的元素都是Map类型,并且Map的key是String类型,Map的value可以是任意类型,我们一般使用HashMap<String, Object>作为List中的数据项。
  • resource表示数据项UI所对应的layout文件,在本例中即R.layout.item。在本例中,每条数据项都要包含图片、名称、描述三条信息,所以我们在item.xml中定义了一个ImageView表示图片,两个TextView分别表示名称和描述,并且都设置了ID值。
  • 每个数据项对应一个Map,from表示的是Map中key的数组。
  • 数据项Map中的每个key都在layout中有对应的View,to表示数据项对应的View的ID数组。
四、SimpleCursorAdapter

SimpleCursorAdapter可以从数据库中读取数据显示在列表上。

五、自定义baseAdapter

参考Android中万能的BaseAdapter的使用来一个未优化版本:
<pre>
package com.tutor.baseadapter;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
public class BaseAdapterDemo extends Activity {

private Spinner mSpinner;  
private ListView mListView;  
private GridView mGridView;  
private MyAdapter mMyAdapter;  
@Override 
public void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.main);  
    setupViews();  
}  
   
public void setupViews(){  
    mMyAdapter = new MyAdapter();  
    mSpinner = (Spinner)findViewById(R.id.spinner);  
    mSpinner.setAdapter(mMyAdapter);  
    mListView = (ListView)findViewById(R.id.listview);  
    mListView.setAdapter(mMyAdapter);  
    mGridView = (GridView)findViewById(R.id.gridview);  
    mGridView.setAdapter(mMyAdapter);  
    mGridView.setNumColumns(2);  
   
}  
   
//定义自己的适配器,注意getCount和getView方法   
private class MyAdapter extends BaseAdapter{  
    @Override 
    public int getCount() {  
        // 这里我就返回10了,也就是一共有10项数据项   
        return 10;  
    }  
    @Override 
    public Object getItem(int arg0) {  
        return arg0;  
    }  
    @Override 
    public long getItemId(int position) {  
        return position;  
    }  
    @Override 
    public View getView(int position, View convertView, ViewGroup parent) {  
        // position就是位置从0开始,convertView是Spinner,ListView中每一项要显示的view   
        //通常return 的view也就是convertView   
        //parent就是父窗体了,也就是Spinner,ListView,GridView了.   
        //TextView mTextView = new TextView(getApplicationContext());  
        //mTextView.setText("BaseAdapterDemo");  
        //mTextView.setTextColor(Color.RED);
        convertView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.baseadapter_provider,null);
        TextView mTextView = (TextView)convertView.findViewById(R.id.textview);
        mTextView.setText("BaseAdapterDemo" + position);
        mTextView.setTextColor(Color.RED);
        return mTextView;  
    }  
       
}  

}
</pre>

优化思路:通过convertView+ViewHolder来实现,使用ViewHolder这个静态类的好处是缓存了显示数据的View,加快了UI的响应速度。当我们判断convertView == null,若为空,就会根据设计好的布局文件布局,并未convertView赋值,并且生成一个viewHolder来绑定convertView的各个View控件。再用convertView的setTag将viewHolder设置到Tag中,以便系统第二次绘制ListView时从Tag中取出。
参考
Android--Adapter深入理解及ListView优化
android代码优化----ListView中自定义adapter的封装

package com.example.listview_baseadapter;  
  
import java.util.ArrayList;  
import java.util.HashMap;  
import java.util.List;  
import java.util.Map;  
  
import android.app.Activity;  
import android.content.Context;  
import android.os.Bundle;  
import android.view.LayoutInflater;  
import android.view.Menu;  
import android.view.View;  
import android.view.ViewGroup;  
import android.widget.BaseAdapter;  
import android.widget.ImageView;  
import android.widget.ListView;  
import android.widget.TextView;  
  
public class MainActivity extends Activity {  
  
    private ListView listView = null;  
    private List<Map<String, Object>> data = null;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        listView = (ListView) findViewById(R.id.list);// 取得控件  
        data = getData();//获取数据  
        MyAdapter adapter = new MyAdapter(this);  
        listView.setAdapter(adapter);  
    }  
  
    @Override  
    public boolean onCreateOptionsMenu(Menu menu) {  
        // Inflate the menu; this adds items to the action bar if it is present.  
        getMenuInflater().inflate(R.menu.main, menu);  
        return true;  
    }  
  
    // 得到数据  
    public List<Map<String, Object>> getData() {  
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();  
  
         Map<String, Object> map;  
            for(int i=0;i<10;i++)  
            {  
                map = new HashMap<String, Object>();  
                map.put("img", R.drawable.bgs);  
                map.put("title", "Coder");  
            map.put("content", "简单Coding,快乐生活~~~~");  
                list.add(map);  
            }  
            return list;  
    }  
  
    // ViewHolder静态类  
    static class ViewHolder {  
        public ImageView img;  
        public TextView title;  
        public TextView content;  
    }  
  
    public class MyAdapter extends BaseAdapter {  
  
        private LayoutInflater mInflater = null;  
  
        private MyAdapter(Context context) {  
            // 根据context上下文加载布局  
            this.mInflater = LayoutInflater.from(context);  
        }  
  
        @Override  
        public int getCount() {  
            // 在此适配器中所代表的数据集中的条目数  
            return data.size();  
        }  
  
        @Override  
        public Object getItem(int position) {  
              
            // 获取数据集中与指定索引对应的数据项  
            return position;  
        }  
  
        @Override  
        public long getItemId(int position) {  
          
            // 获取在列表中与指定索引对应的行id  
            return position;  
        }  
  
        // 获取一个在数据集中指定索引的视图来显示数据  
        @Override  
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;  
            // 如果缓存convertView为空,则需要创建View  
            if (convertView == null) {  
                holder = new ViewHolder();  
                // 根据自定义的Item布局加载布局  
                convertView = mInflater.inflate(R.layout.list_item, null);  
                holder.img = (ImageView) convertView.findViewById(R.id.img);  
                holder.title = (TextView) convertView.findViewById(R.id.title);  
                holder.content = (TextView) convertView.findViewById(R.id.content);  
                // 将设置好的布局保存到缓存中,并将其设置在Tag里,以便后面方便取出Tag  
                convertView.setTag(holder);  
            } else {  
                holder = (ViewHolder) convertView.getTag();  
            }  
            holder.img.setBackgroundResource((Integer) data.get(position).get("img"));  
            holder.title.setText((String) data.get(position).get("title"));
            holder.content.setText((String)data.get(position).get("content"));  
  
            return convertView;  
        }  
  
    }  
  
}

注意这段代码:

    // ViewHolder静态类  
    static class ViewHolder {  
        public ImageView img;  
        public TextView title;  
        public TextView content;  
    } 

参考android listview 声明ViewHolder内部类时,为什么建议使用static关键字
这个问题也是我每次面试别人必问的问题之一。其实这个是考静态内部类和非静态内部类的主要区别之一。非静态内部类会隐式持有外部类的引用,就像大家经常将自定义的adapter在Activity类里,然后在adapter类里面是可以随意调用外部activity的方法的。当你将内部类定义为static时,你就调用不了外部类的实例方法了,因为这时候静态内部类是不持有外部类的引用的。声明ViewHolder静态内部类,可以将ViewHolder和外部类解引用。大家会说一般ViewHolder都很简单,不定义为static也没事吧。确实如此,但是如果你将它定义为static的,说明你懂这些含义。万一有一天你在这个ViewHolder加入一些复杂逻辑,做了一些耗时工作,那么如果ViewHolder是非静态内部类的话,就很容易出现内存泄露。如果是静态的话,你就不能直接引用外部类,迫使你关注如何避免相互引用。 所以将 ViewHolder内部类 定义为静态的,是一种好习惯.

另外,使用使用静态内部类实现单例模式,这种方法也是《Effective Java》上所推荐的

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

六、使用技巧

参考《安卓群英传》P68
1.设置项目分隔线

android:divider="@android:color/darker_gray"
android:dividerHeight="10dp"

2.隐藏滚动条
android:scrollbars="none"
3.取消点击效果,可以使用安卓自带透明色
android:listSelector="@android:color/transparent"
4.设置显示在第几项
默认显示第一项,可以使用listView.setSelection(N)设置显示第N项。
当然这个方法类似scrollTo,瞬间完成移动。缓动可以使用:

mListView.smoothScrollBy(distance,duration);
mListView.smoothScrollbyOffset(offset);
mListView.smoothScrollToPosition(index);

5.动态修改

mData.add("new");
mAdapter.notifyDataSetChanged();
mListView.setSelection(mData.size() - 1);

6.遍历

for(int i = 0; i < mListView.getChildCount();i ++){
   View view = mListView.getChildAt(i);
}

7.设置空数据时如何显示

listView.setEmptyView(findViewById(R.id.empty_view));

8.点击

listView.setOnItemClickListener(new OnItemClickListener(){
   @Override
   public void onItemClick(AdapterView<?> parent, View view, int position, long id){
      Fruit fruit = fruitList.get(position);//通过position判断用户点击的是哪一个子项
      Toast.makeText...
   }
);

9.onTouchListener
10.onScrollListener

//通过不同状态来设置一些flag,来区分不同的滑动状态,供其他方法处理
mListView.setOnScrollListener(new OnScrollListener(){
   public void onScrollStateChanged(AbsListView view, int scrollState){
      switch(scrollState){
         case OnScrollListener.SCROLL_STATE_IDLE:
         //滑动停止时
         break;
         case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
         //正在滚动
         break;
         case OnScrollListener.SCROLL_STATE_FLING:
         //手指抛动时 离开listview后由于惯性继续滑动
         break;
      }
   }
});

//onScroll在listview滚动时会一直回调
public void onScroll(AbsListView view,int firstVisibleItem,int visibleItemCount,int totalItemCount){
   //firstVisibleItem 当前能看见的第一个Item的ID,从0开始
   //visibleItemCount 当前能看见的Item总数
   //totalItemCount 整个ListView的Item总数(包括没有显示完整的Item)
   
   if(firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0){
      //滚动到最后一行
   }
   
   if(firstVisibleItem > lastVisibleItemPosition){
      //上滑
   }else if(firstVisibleItem < lastVisibleItemPosition){
      //下滑
   }
   lastVisibleItemPosition = firstVisibleItem;
   
   //获取可视区域内最后一个Item的id
   mListView.getLastVisiblePosition()
   //获取可视区域内第一个Item的id
   mListView.getFirstVisiblePosition()
}

11.优化卡顿-参考《安卓开发艺术探索》
首先,不要在getView中执行耗时操作。比如加载图片,必须要用异步方式处理。
其次,控制异步操作的执行频率。如果用户刻意频繁上下滑动,会在一瞬间产生上百个异步任务,这会造成线程池拥堵并随即带来大量的UI更新,这是没有意义的。可以在列表滑动时停止加载图片,静止时再加载。

public void onScrollStateChanged(AbsListView view,int scrollState){
   if(scrollState == OnScrollListener.SCROLL_STATE_IDLE){
      mIsGridViewIdle = true;
      mImageAdapter.notifyDataSetChanged();
   }else{
      mIsGridViewIdle = false;
   }
}

//在getView方法中,仅当列表静止时才加载图片:
if(mIsGridViewIdle && mCanGetBitmapFromNetWrok){
   imageView.setTage(uri);
   mImageLoader.bindBitmap(uri,imageView,mImageWidth,mImageWidth);
}

另外,可以开启硬件加速来解决莫名的卡顿问题。通过设置android:hardwareAccelerated = "true"即可为Activity开启硬件加速。

七、常用扩展

1.使用maxOverScrollY属性,让列表滚动到底端或顶端后,继续向下或向上滑动一段距离。

private void initView() {
        //让不同分辨率弹性滑动距离基本一致
        DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
        float density = metrics.density;
        mMaxOverDistance = (int) (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);
    }

2.向下滑动时,标题栏和悬浮按钮自动消失,让用户有更大空间阅读。

package com.imooc.myapplication;

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.ArrayAdapter;
import android.widget.ListView;


public class ScrollHideListView extends Activity {

    private Toolbar mToolbar;
    private ListView mListView;
    private String[] mStr = new String[20];
    private int mTouchSlop;
    private float mFirstY;
    private float mCurrentY;
    private int direction;
    private ObjectAnimator mAnimator;
    private boolean mShow = true;

    View.OnTouchListener myTouchListener = 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();
                    if (mCurrentY - mFirstY > mTouchSlop) {
                        direction = 0;// down
                    } else if (mFirstY - mCurrentY > mTouchSlop) {
                        direction = 1;// up
                    }
                    if (direction == 1) {
                        if (mShow) {
                            toolbarAnim(1);//show
                            mShow = !mShow;
                        }
                    } else if (direction == 0) {
                        if (!mShow) {
                            toolbarAnim(0);//hide
                            mShow = !mShow;
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    break;
            }
            return false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.scroll_hide);
        //系统认为最低滑动距离,超过此值就是滑动状态
        mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
        mToolbar = (Toolbar) findViewById(R.id.toolbar);
        mListView = (ListView) findViewById(R.id.listview);
        for (int i = 0; i < mStr.length; i++) {
            mStr[i] = "Item " + i;
        }
        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)));
        mListView.addHeaderView(header);
        mListView.setAdapter(new ArrayAdapter<String>(
                ScrollHideListView.this,
                android.R.layout.simple_expandable_list_item_1,
                mStr));
        mListView.setOnTouchListener(myTouchListener);
    }

    private void toolbarAnim(int flag) {
        //位移属性动画
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.cancel();
        }
        if (flag == 0) {
            mAnimator = ObjectAnimator.ofFloat(mToolbar,
                    "translationY", mToolbar.getTranslationY(), 0);
        } else {
            mAnimator = ObjectAnimator.ofFloat(mToolbar,
                    "translationY", mToolbar.getTranslationY(),
                    -mToolbar.getHeight());
        }
        mAnimator.start();
    }
}

3.像微信聊天,区分收到消息和发送消息的两种布局

package com.imooc.myapplication;

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;

public class ChatItemListViewAdapter extends BaseAdapter {

    private List<ChatItemListViewBean> mData;
    private LayoutInflater mInflater;

    public ChatItemListViewAdapter(Context context,
                                   List<ChatItemListViewBean> data) {
        this.mData = data;
        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 int getItemViewType(int position) {
        //用来返回第position个Item是何类型
        ChatItemListViewBean bean = mData.get(position);
        return bean.getType();
    }

    @Override
    public int getViewTypeCount() {
        //用来返回不同布局的总数
        return 2;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            //根据类型生成不同的布局
            if (getItemViewType(position) == 0) {
                holder = new ViewHolder();
                convertView = mInflater.inflate(
                        R.layout.chat_item_itemin, null);
                holder.icon = (ImageView) convertView.findViewById(
                        R.id.icon_in);
                holder.text = (TextView) convertView.findViewById(
                        R.id.text_in);
            } else {
                holder = new ViewHolder();
                convertView = mInflater.inflate(
                        R.layout.chat_item_itemout, null);
                holder.icon = (ImageView) convertView.findViewById(
                        R.id.icon_out);
                holder.text = (TextView) convertView.findViewById(
                        R.id.text_out);
            }
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.icon.setImageBitmap(mData.get(position).getIcon());
        holder.text.setText(mData.get(position).getText());
        return convertView;
    }

    public final class ViewHolder {
        public ImageView icon;
        public TextView text;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容