delete from hateblo.jp where 1=1;

タイトルに意味はありません。

StickyListHeaders を使っての雑感

概要

コンセプトととしてすばらしく、リストの区切りごとに見出しをつけることができる。
使いやすい点として、Adapterへの実装がメインで上位に対して影響を与えないということがとてもすばらしく、多くのアプリで採用されているみたい。
そこで、よくやることとはまったことなどを記載。

ListFragmentへの実装

とりあえず以下の実装

stickylistheaderslist.xml
<?xml version="1.0" encoding="utf-8"?>
<com.emilsjolander.components.stickylistheaders.StickyListHeadersListView
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@android:id/list"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:orientation="vertical" />
IssueView.java

onCreateViewとAdapterにExampleViewStickyListHeadersAdapterを使うこと以外は通常のListFragmentと同じ。

public class IssueView extends ListFragment {

	private ExampleViewStickyListHeadersAdapter adapter;
	public IssueView(){
		super();
	}

	@Override
	public void onDestroyView() {
		setListAdapter(null);
		super.onDestroyView();
	}


	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);

		//getListView().addFooterView(mFooter);

		adapter = new ExampleViewStickyListHeadersAdapter();
		
		setListAdapter(adapter);
		
		getListView().setFastScrollEnabled(true);

		onRefresh(true);
	}
	protected void onRefresh(boolean isFetchRemote){
		//adapter.setupParameter(intent.getConnectionId(),issue_id);
		adapter.notifyDataSetInvalidated();
		adapter.notifyDataSetChanged();

	}
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		//mFooter = inflater.inflate(R.layout.listview_footer,null);
		//mFooter.setVisibility(View.GONE);
		return inflater.inflate(R.layout.stickylistheaderslist, container, false);
	}
}
ExampleViewStickyListHeadersAdapter.java

BaseAdapterとほぼほぼ同じ。
具体的な違いとしてはgetHeaderViewでヘッダービューを作っているところとgetHeaderIdでIDを返していること。
getHeaderIdで戻るIDでViewを作るか判断しているような気がする。(必要確認)

public class ExampleViewStickyListHeadersAdapterextends BaseAdapter implements StickyListHeadersAdapter {
	
	@Override
	public View getHeaderView(int position, View convertView, ViewGroup parent) {
		if (convertView == null) {
			LayoutInflater infalInflater = (LayoutInflater) parent.getContext()
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = infalInflater.inflate(R.layout.issuestickyheader, null);
		}
		if (convertView != null){
			//必ず実装
			TextView text = (TextView) convertView.findViewById(R.id.textTitle);
			text.setText("Header");
		}
		return convertView;
	}

	@Override
	public long getHeaderId(int pos) {
		//必ず実装
		//posはgetItemと同じ値
	}

	@Override
	public int getCount() {
		//必ず実装
	}

	@Override
	public int getItemViewType(int pos) {
		//必要に応じて実装
	}
	

	@Override
	public Object getItem(int pos) {
		//必ず実装
	}

	@Override
	public long getItemId(int pos) {
		return pos; //必要に応じて実装
	}

	@Override
	public View getView(int pos, View convertView, ViewGroup parent) {
		if (convertView == null) {
			LayoutInflater infalInflater = (LayoutInflater) parent.getContext()
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = infalInflater.inflate(android.R.layout.simple_list_item_1, null);
		}
		//普通に実装
		return convertView;
	}
	
	@Override
	public View getDropDownView(int pos, View convertView, ViewGroup parent) {
		if (convertView == null) {
			LayoutInflater infalInflater = (LayoutInflater) parent.getContext()
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = infalInflater.inflate(android.R.layout.simple_spinner_dropdown_item, null);
		}
		//普通に実装
		return convertView;
	}
}

いいね!マージドにしようよ!

複数のAdapterを束ねればすばらしいビューができるのではないか。

ExampleViewStickyListHeadersAdapter.java

注意点としてはgetItemViewTypeで子アダプターのビュータイプIDを返すので、子アダプターには実装しておく必要がある。
くわしくは、getItemViewTypeの説明を参照。

public class ExampleViewStickyListHeadersAdapterextends BaseAdapter implements StickyListHeadersAdapter {
	
	private final List<AggrigateAdapter> mapAdapters = new ArrayList<AggrigateAdapter>();
	protected class AggrigateAdapter {
		public final BaseAdapter adapter;
		public int count;
		public int head;
		public final int res;
		public AggrigateAdapter(BaseAdapter a, int r){
			adapter = a;
			res = r; //タイトルをつけたいので、StringのリソースIDを設定
		}
		public int getInnerPos(int pos){
			return pos - head;
		}
		public Object getItem(int pos){
			return adapter == null ? null : adapter.getItem(getInnerPos(pos));
		}
		public long getItemId(int pos){
			return adapter == null ? 0 : adapter.getItemId(getInnerPos(pos));
		}
		public View getView(int pos, View convertView, ViewGroup parent) {
			return adapter == null ? null : adapter.getView(getInnerPos(pos), convertView, parent);
		}
		public View getDropDownView(int pos, View convertView, ViewGroup parent) {
			return adapter == null ? null : adapter.getDropDownView(getInnerPos(pos), convertView, parent);
		}
		public int getItemViewType(int pos) {
			return adapter == null ? 0 : adapter.getItemViewType(getInnerPos(pos));
		}
	}


	public ExampleViewStickyListHeadersAdapterextends (){
		//ここで、adapterなどを追加する
		mapAdapters.add(new AggrigateAdapter(adapterIssue, R.string.ticket_detail));
		mapAdapters.add(new AggrigateAdapter(adapterJournal, R.string.ticket_journals));
	}

	@Override
	public void notifyDataSetChanged() {

		int current = 0;
		for(AggrigateAdapter adapter : mapAdapters){
			adapter.adapter.notifyDataSetChanged();
			adapter.head = current;
			adapter.count = adapter.adapter.getCount();
			current += adapter.count;
		}
		super.notifyDataSetChanged();
	}
	protected AggrigateAdapter getInner(int pos){
		AggrigateAdapter lastadapter = null;
		for(AggrigateAdapter item : mapAdapters){
			if(item.head <= pos){
				lastadapter = item;
			}
		}
		//returns default
		return lastadapter;
	}
	@Override
	public void notifyDataSetInvalidated() {
		if(mapAdapters != null){
			for(AggrigateAdapter adapter : mapAdapters){
				adapter.adapter.notifyDataSetInvalidated();
			}
		}
		super.notifyDataSetInvalidated();
	}
	@Override
	public View getHeaderView(int position, View convertView, ViewGroup parent) {
		if (convertView == null) {
			LayoutInflater infalInflater = (LayoutInflater) parent.getContext()
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = infalInflater.inflate(R.layout.issuestickyheader, null);
		}
		if (convertView != null){
			//必ず実装
			TextView text = (TextView) convertView.findViewById(R.id.textTitle);
			text.setText("Header");
		}
		return convertView;
	}
	@Override
	public View getHeaderView(int position, View convertView, ViewGroup parent) {
		if (convertView == null) {
			LayoutInflater infalInflater = (LayoutInflater) parent.getContext()
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = infalInflater.inflate(R.layout.issuestickyheader, null);
		}
		if (convertView != null){
			TextView text = (TextView) convertView.findViewById(R.id.textTitle);
			AggrigateAdapter adapter = getInner(position);
			text.setText(adapter == null ? "" : convertView.getContext().getString(getInner(position).res));
		}
		return convertView;
	}

	@Override
	public long getHeaderId(int pos) {
		AggrigateAdapter adapter = getInner(pos);
		return adapter == null ? 0 : adapter.res;
	}

	@Override
	public int getCount() {
		AggrigateAdapter lastadapter = null;
		for(AggrigateAdapter item : mapAdapters){
			lastadapter = item;
		}
		return lastadapter == null ? 0 : lastadapter.head + lastadapter.count;
	}
	

	@Override
	public int getItemViewType(int pos) {
		//ここを実装しておくと、子アダプタのgetView内部で自分のViewなのかどうか
		//不要になるので実装することをお勧めする。
		//getItemViewTypeで返すのはlayoutのIDでよい
		AggrigateAdapter adapter = getInner(pos);
		return adapter == null ? android.R.layout.simple_list_item_1 : adapter.getItemViewType(pos);
	}

	//getViewTypeCountは実装しない方針。
	//Header部分も含めて、全部で何種類なのかを把握しないと、ArrayIndexOutOfBoundsException: length=5; index=2130903051とかあっさり起こる

	@Override
	public Object getItem(int pos) {
		return getInner(pos).getItem(pos);
	}

	@Override
	public long getItemId(int pos) {
		return pos; //必要に応じて実装
	}

	@Override
	public View getView(int pos, View convertView, ViewGroup parent) {
		AggrigateAdapter adapter = getInner(pos);
		convertView = adapter == null ? null : adapter.getView(pos, convertView, parent);
		if (convertView == null) {
			LayoutInflater infalInflater = (LayoutInflater) parent.getContext()
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = infalInflater.inflate(android.R.layout.simple_list_item_1, null);
		}
		//普通に実装
		return convertView;
	}
	
	@Override
	public View getDropDownView(int pos, View convertView, ViewGroup parent) {
		AggrigateAdapter adapter = getInner(pos);
		convertView = adapter == null ? null : adapter.getDropDownView(pos, convertView, parent);
		if (convertView == null) {
			LayoutInflater infalInflater = (LayoutInflater) parent.getContext()
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = infalInflater.inflate(android.R.layout.simple_spinner_dropdown_item, null);
		}
		//普通に実装
		return convertView;
	}
}
参考: 安易にgetViewTypeCountを実装した場合
 FATAL EXCEPTION: main
 java.lang.ArrayIndexOutOfBoundsException: length=5; index=2130903051
 	at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:6171)
 	at android.widget.ListView.layoutChildren(ListView.java:1800)
 	at android.widget.AbsListView.onLayout(AbsListView.java:1939)
 	at com.emilsjolander.components.stickylistheaders.StickyListHeadersListView.onLayout(StickyListHeadersListView.java:142)
 	at android.view.View.layout(View.java:11359)
 	at android.view.ViewGroup.layout(ViewGroup.java:4571)
 	at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
 	at android.view.View.layout(View.java:11359)
 	at android.view.ViewGroup.layout(ViewGroup.java:4571)
 	(中略)
 	at android.view.View.layout(View.java:11359)
 	at android.view.ViewGroup.layout(ViewGroup.java:4571)
 	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1673)
 	at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2711)
 	at android.os.Handler.dispatchMessage(Handler.java:99)
 	at android.os.Looper.loop(Looper.java:156)
 	at android.app.ActivityThread.main(ActivityThread.java:5099)
 	at java.lang.reflect.Method.invokeNative(Native Method)
 	at java.lang.reflect.Method.invoke(Method.java:511)
 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:991)
 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:758)
 	at dalvik.system.NativeStart.main(Native Method)
参考文献


ほかに何かあったはずだけど、思い出せないので、思い出したら追記する。