第一種原生方法
activity布局 ll_top代表要懸停的部分 這里面我放了 圖片和文本

1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout 3 xmlns:android="http://schemas.android.com/apk/res/android" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:paddingBottom="@dimen/activity_vertical_margin" 8 android:paddingLeft="@dimen/activity_horizontal_margin" 9 android:paddingRight="@dimen/activity_horizontal_margin" 10 android:paddingTop="@dimen/activity_vertical_margin" 11 tools:context="com.demon.testservice.ui.RelistActivity"> 12 13 <FrameLayout 14 android:layout_width="match_parent" 15 android:layout_height="match_parent"> 16 17 <android.support.v7.widget.RecyclerView 18 android:id="@+id/recyclerView" 19 android:layout_width="match_parent" 20 android:layout_height="match_parent"> 21 22 </android.support.v7.widget.RecyclerView> 23 24 25 <LinearLayout 26 android:id="@+id/ll_top" 27 android:layout_width="match_parent" 28 android:layout_height="wrap_content" 29 android:orientation="vertical"> 30 31 32 <LinearLayout 33 android:layout_width="match_parent" 34 android:layout_height="wrap_content" 35 android:background="@color/white" 36 android:gravity="center_vertical" 37 android:orientation="horizontal"> 38 39 <ImageView 40 android:layout_width="30dp" 41 android:layout_height="30dp" 42 android:src="@drawable/ic_launcher"/> 43 44 <TextView 45 android:id="@+id/tv_top" 46 android:layout_width="wrap_content" 47 android:layout_height="wrap_content" 48 android:text="name"/> 49 50 </LinearLayout> 51 52 53 <View 54 android:id="@+id/top_divider" 55 android:layout_width="match_parent" 56 android:layout_height="0.2dp" 57 android:background="#33000000"/> 58 59 </LinearLayout> 60 61 62 </FrameLayout> 63 64 65 </RelativeLayout>
item布局 上面和ll_top一樣,下面部分是顯示一張大圖片

1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:orientation="vertical" 6 > 7 8 <LinearLayout 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:gravity="center_vertical" 12 android:orientation="horizontal" 13 android:background="@color/white" 14 > 15 16 <ImageView 17 android:layout_width="30dp" 18 android:layout_height="30dp" 19 android:src="@drawable/ic_launcher"/> 20 21 <TextView 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:text="name"/> 25 26 </LinearLayout> 27 28 <View 29 30 android:layout_width="match_parent" 31 android:layout_height="0.2dp" 32 android:background="#33000000" /> 33 34 35 <ImageView 36 android:paddingTop="5dp" 37 android:layout_width="wrap_content" 38 android:layout_height="wrap_content" 39 android:src="@drawable/user"/> 40 41 42 </LinearLayout>
activity 設置addOnScrollListener 是關鍵
1 public class RelistActivity extends AppCompatActivity { 2 3 @Bind(R.id.recyclerView) 4 RecyclerView recyclerView; 5 6 @Bind(R.id.ll_top) 7 LinearLayout ll_top; 8 9 @Bind(R.id.tv_top) 10 TextView tv_top; 11 12 private int height; 13 private int currentPosition = 0; 14 15 @Override 16 protected void onCreate(Bundle savedInstanceState) { 17 super.onCreate(savedInstanceState); 18 setContentView(R.layout.activity_relist); 19 ButterKnife.bind(this); 20 21 final LinearLayoutManager linearLayoutManager=new LinearLayoutManager(this); 22 RelistAdapter adapter=new RelistAdapter(); 23 24 recyclerView.setLayoutManager(linearLayoutManager); 25 recyclerView.setAdapter(adapter); 26 27 28 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 29 @Override 30 public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 31 super.onScrollStateChanged(recyclerView, newState); 32 33 height=ll_top.getHeight(); 34 35 } 36 37 @Override 38 public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 39 super.onScrolled(recyclerView, dx, dy); 40 View view=linearLayoutManager.findViewByPosition(currentPosition+1); 41 if (view==null)return; 42 43 if (view.getTop()<=height){ 44 ll_top.setY(-(height-view.getTop())); 45 }else { 46 ll_top.setY(0); 47 } 48 49 50 if (currentPosition!=linearLayoutManager.findFirstVisibleItemPosition()){ 51 currentPosition=linearLayoutManager.findFirstVisibleItemPosition(); 52 ll_top.setY(0); 53 54 tv_top.setText(" "+currentPosition); 55 } 56 } 57 }); 58 59 } 60 61 62 63 64 65 66 67 class RelistAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{ 68 69 70 @Override 71 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 72 View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_re,parent,false); 73 return new ReHolder(view); 74 } 75 76 @Override 77 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 78 79 } 80 81 @Override 82 public int getItemCount() { 83 return 10; 84 } 85 86 class ReHolder extends RecyclerView.ViewHolder{ 87 88 public ReHolder(View itemView) { 89 super(itemView); 90 ButterKnife.bind(this,itemView); 91 } 92 } 93 } 94 95 96 }
第二種
timehop/sticky-headers-recyclerview: [UNMAINTAINED] Sticky Headers decorator for Android's RecyclerView
https://github.com/timehop/sticky-headers-recyclerview
這個庫好在對adapter改動小,只需要實現就行了
這里用 一個省份城市api 測試 http://apistore.baidu.com/apiworks/servicedetail/990.html
取以下四種數據 並添加一個 string類型的 type
"province": "上海",
"region": "華東地區",
"x": 121.473,
"y": 31.2317
CityInfo 是adapter 需要的數據

1 public class CityInfo { 2 /* 3 華東地區 a 4 華北地區 b 5 華南地區 c 6 華中地區 d 7 8 東北地區 e 9 西北地區 f 10 西南地區 g 11 12 台港澳地區 h 13 */ 14 15 private String province; 16 private String region; 17 private String type; 18 private double x; 19 private double y; 20 21 public String getType() { 22 return type; 23 } 24 25 public void setType(String type) { 26 this.type = type; 27 } 28 29 public String getProvince() { 30 return province; 31 } 32 33 public void setProvince(String province) { 34 this.province = province; 35 } 36 37 public String getRegion() { 38 return region; 39 } 40 41 public void setRegion(String region) { 42 this.region = region; 43 } 44 45 public double getX() { 46 return x; 47 } 48 49 public void setX(double x) { 50 this.x = x; 51 } 52 53 public double getY() { 54 return y; 55 } 56 57 public void setY(double y) { 58 this.y = y; 59 } 60 }
獲取json依然用rxjava和Retrofit city對象還是GsonFormat自動解析的 代碼就不貼了
public class NetWork { private static CityApi cityApi; public static CityApi getCityApi(){ if (cityApi==null){ Retrofit retrofit=new Retrofit.Builder().baseUrl("http://apis.baidu.com/").addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build(); cityApi=retrofit.create(CityApi.class); } return cityApi; } }
public interface CityApi { //http://apis.baidu.com/tngou/factory/province @GET("tngou/factory/province") rx.Observable<City>getCity(@Header("apikey")String apikey); }
主布局就一個RecyclerView ,懸停布局一個textview ,item 布局三個textview

1 <?xml version="1.0" encoding="utf-8"?> 2 <TextView 3 android:id="@+id/item_headtv" 4 xmlns:android="http://schemas.android.com/apk/res/android" 5 xmlns:tools="http://schemas.android.com/tools" 6 android:layout_width="match_parent" 7 android:layout_height="wrap_content" 8 android:padding="10dp" 9 tools:text="吸頂文本" 10 android:background="@color/hole_bule" 11 />

1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:padding="11dp" 6 android:background="@color/blue_semi_transparent_pressed" 7 > 8 9 <TextView 10 android:id="@+id/tv_sticky_province" 11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content" 13 android:layout_alignParentLeft="true" 14 android:layout_centerVertical="true" 15 android:text="province" 16 /> 17 18 <TextView 19 android:id="@+id/tv_sticky_x" 20 android:layout_width="wrap_content" 21 android:layout_height="wrap_content" 22 android:layout_alignParentRight="true" 23 android:text="X" 24 /> 25 26 <TextView 27 android:id="@+id/tv_sticky_y" 28 android:layout_width="wrap_content" 29 android:layout_height="wrap_content" 30 android:layout_alignParentRight="true" 31 android:layout_below="@+id/tv_sticky_x" 32 android:text="Y" 33 /> 34 </RelativeLayout>
adapter 一如既往 繼承 RecyclerView.Adapter 並實現 StickyRecyclerHeadersAdapter 重寫head的三個方法
onBindHeaderViewHolder onCreateHeaderViewHolder 和 RecyclerView的一樣 創建分組的view 綁定分組的數據
getHeaderId 稍微有點麻煩 返回值類型為long 所以我在最開始 CityInfo 多設置了一個type 如果api自帶那就輕松了 。代碼中返回-1的意思是為了把位置為0的item設置成置頂,並不會被分組
1 public class StickyAdapter2 extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements StickyRecyclerHeadersAdapter { 2 3 private List<CityInfo>list=new ArrayList<CityInfo>(); 4 5 public void add(CityInfo object) { 6 list.add(object); 7 notifyDataSetChanged(); 8 } 9 10 public void addAll(Collection<? extends CityInfo> collection){ 11 if (collection!=null){ 12 list.addAll(collection); 13 notifyDataSetChanged(); 14 } 15 } 16 17 @Override 18 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 19 View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.item_stick2,parent,false); 20 return new ItemHolder(view); 21 } 22 23 @Override 24 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 25 if (holder instanceof ItemHolder){ 26 ItemHolder itemHolder= (ItemHolder) holder; 27 itemHolder.tv_province.setText(list.get(position).getProvince()+" "+getItem(position)); 28 29 itemHolder.tv_y.setText("Y軸 "+list.get(position).getY()); 30 itemHolder.tv_x.setText("X軸 "+list.get(position).getX()); 31 } 32 33 } 34 35 @Override 36 public long getHeaderId(int position) { 37 if (position==0){ 38 return -1; 39 }else { 40 return getItem(position).charAt(0); 41 } 42 } 43 44 public String getItem(int position) { 45 return list.get(position).getType(); 46 } 47 48 @Override 49 public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup viewGroup) { 50 View view=LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_headview,viewGroup,false); 51 return new RecyclerView.ViewHolder(view) { 52 53 }; 54 } 55 56 @Override 57 public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder, int position) { 58 TextView textView = (TextView) holder.itemView; 59 textView.setText(list.get(position).getRegion()); 60 // holder.itemView.setBackgroundColor(getRandomColor()); 61 } 62 63 private int getRandomColor() { 64 SecureRandom rgen = new SecureRandom(); 65 return Color.HSVToColor(150, new float[]{ 66 rgen.nextInt(359), 1, 1 67 }); 68 } 69 70 @Override 71 public int getItemCount() { 72 return list.size(); 73 } 74 75 class ItemHolder extends RecyclerView.ViewHolder{ 76 77 @Bind(R.id.tv_sticky_province) 78 TextView tv_province; 79 @Bind(R.id.tv_sticky_x) 80 TextView tv_x; 81 @Bind(R.id.tv_sticky_y) 82 TextView tv_y; 83 84 public ItemHolder(View itemView) { 85 super(itemView); 86 ButterKnife.bind(this,itemView); 87 } 88 } 89 90 }
再看邏輯代碼
StickyRecyclerHeadersDecoration 很重要 一定要設置給recyclerView 不然就不會有head了。 可以發現 它繼承了 ItemDecoration,后者通常 是為每個Item視圖添加子視圖,被用來繪制Divider,可以用,可以不用
添加的head 如果想 相應點擊事件,那就要用到 StickyRecyclerHeadersTouchListener,構造參數是 recyclerView 和 StickyRecyclerHeadersDecoration ,得到實例 再用 setOnHeaderClickListener 即可
如果只想獲取整個item的點擊事件,可以直接用 樣品 33到39行 就搞定了
添加item之間的間隙 可以用 這個文件 recyclerView.addItemDecoration(new DividerDecoration(mContext));
第一個數據我自行添加了一個,為了看置頂的效果,后面的list數據 先根據地區信息設置了我需要的type ,然后進行了一下根據type字母的排序,不然傳進去list很亂
1 public class Sticky2Activity extends BaseActivity { 2 3 @Bind(R.id.rcv_stick) 4 RecyclerView recyclerView; 5 6 private List<CityInfo> list = new ArrayList<CityInfo>(); 7 private StickyAdapter2 adapter; 8 private StickyRecyclerHeadersDecoration headersDecoration; 9 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 setContentView(R.layout.activity_sticky2); 14 ButterKnife.bind(this); 15 adapter = new StickyAdapter2(); 16 headersDecoration=new StickyRecyclerHeadersDecoration(adapter); 17 18 recyclerView.setLayoutManager(new LinearLayoutManager(mContext)); 19 recyclerView.setAdapter(adapter); 20 // recyclerView.addItemDecoration(new StickyRecyclerHeadersDecoration(adapter)); 21 recyclerView.addItemDecoration(headersDecoration); 22 recyclerView.setItemAnimator(new DefaultItemAnimator()); 23 recyclerView.addItemDecoration(new DividerDecoration(mContext)); 24 25 StickyRecyclerHeadersTouchListener headersTouchListener=new StickyRecyclerHeadersTouchListener(recyclerView,headersDecoration); 26 headersTouchListener.setOnHeaderClickListener(new StickyRecyclerHeadersTouchListener.OnHeaderClickListener() { 27 @Override 28 public void onHeaderClick(View view, int position, long headerId) { 29 showToast("Header position: " + position + ", id: " + headerId); 30 } 31 }); 32 33 recyclerView.addOnItemTouchListener(headersTouchListener); 34 recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(mContext, new RecyclerItemClickListener.OnItemClickListener() { 35 @Override 36 public void onItemClick(View view, int position) { 37 showToast("position "+position); 38 } 39 })); 40 41 42 NetWork.getCityApi().getCity("f44bd76826571234e2616746eabf3c3c").subscribeOn(Schedulers.io()).observeOn 43 (AndroidSchedulers.mainThread()).subscribe(new Action1<City>() { 44 45 @Override 46 public void call(City city) { 47 if (city.isStatus()) { 48 int size = city.getTngou().size(); 49 CityInfo info = new CityInfo(); 50 info.setProvince("USA"); 51 info.setRegion("西南地區"); 52 info.setX(20.032); 53 info.setY(20.032); 54 info.setType("a"); 55 list.add(info); 56 57 for (int i = 0; i < size; i++) { 58 City.TngouBean bean = city.getTngou().get(i); 59 CityInfo cityInfo = new CityInfo(); 60 cityInfo.setProvince(bean.getProvince()); 61 cityInfo.setRegion(bean.getRegion()); 62 cityInfo.setX(bean.getX()); 63 cityInfo.setY(bean.getY()); 64 cityInfo.setType(settype(bean.getRegion())); 65 list.add(cityInfo); 66 } 67 Collections.sort(list, new Comparator<CityInfo>() { 68 @Override 69 public int compare(CityInfo lhs, CityInfo rhs) { 70 String a=lhs.getType(); 71 String b=rhs.getType(); 72 return a.compareToIgnoreCase(b); 73 } 74 }); 75 adapter.addAll(list); 76 } 77 } 78 }); 79 } 80 81 private String settype(String region) { 82 switch (region) { 83 case "華東地區": 84 return "a"; 85 case "華北地區": 86 return "b"; 87 case "華南地區": 88 return "c"; 89 case "華中地區": 90 return "d"; 91 case "東北地區": 92 return "e"; 93 case "西北地區": 94 return "f"; 95 case "西南地區": 96 return "g"; 97 default: 98 case "台港澳地區": 99 return "h"; 100 } 101 } 102 }
FastAdapter 這個貌似也不錯 以后學習