Android 自己动手写ListView学习其原理 1 显示第一屏Item


《Android 自己动手写ListView学习其原理 1 显示第一屏Item》
 《Android 自己动手写ListView学习其原理 2 上下滚动》

《Android 自己动手写ListView学习其原理 3 ItemClick,ItemLongClick,View复用》


      之前分析ListView初始化写的一篇文章 《Android ListView初始化简单分析》,现在按照Android ListView的原理自己动手写一个ListView,因为分析源码总有可能一些地方看到了也不一定注意,即使注意自己理解的还原本的含义也不一定一样,自己动手印象也更深刻,ListView是Android中使用频率非常高的控件,其内部实现也很负责,涉及视添加,滚动,抛掷滑动Fling,点击(ItemClick),按压Item(Pressed)等处理,当前文章是一个初步实现,仅能把第一屏的Item给显示出来,其他特性都不支持。


一、有图有真相    自己写一个ListView即使再像,它还是ListView的样子。。。



二、简单分析实现步骤

    先想想平时是怎么使用ListView控件的,Data + Adapter(getView), ListView 涉及适配器模式,即 数据和视图由开发者创建 Adapter把不同的数据和视图匹配到ListView进行展示,既然当前是自己写ListView前两个Data 与 Adapter还是使用复用实现实现就行,没必要重新涉及这两块。

     主要实现ListView另外两个实现方式和之前相同就行,先来看看ListView继承结构:

ListView  --extends-->  AbsListview  --extends-->  AdapterView  --extends-->  ViewGroup  --extends-->  View

     看了ListView的继承结构,要是自己写要继承自己哪一个呢?应该如何选择?

     1. 从最顶层父类开始看吧,自定义视图都必须继承自View这是无容置疑的,自己写肯定要继承自己View。

    2. 接着看是否要继承自其子类ViewGroup?实现ListView必须要继承自ViewGroup因为其自身必须可以包含子视图,是否要继承自AdapterView?

    3. 是否要继承自ViewGroup子类AdapterView ? 这个是可以选择的,可以继承也可以不继承没有强制性,不过AdapterView主要是把数据和和视图绑定到ListView 和 一些记录操作等处理,其实也没必要维护直接继承。

    结论:ListView常用使用中需要Data + Adapter(getView), ListView 三部分,其中前两项和之前使用ListView一样,主需要处理自定义ListView即可,而且自定义的ListView可以直接自称自AdapterView。


三、上代码

1. 先来看看在Activity写的Data + Adapter ,与常用ListView使用相同没什么特别的。

public class MainActivity extends Activity {

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ListView3D listView = (ListView3D) findViewById(R.id.my_listview);
        CustomAdapter customAdapter = new CustomAdapter(this, createTestData());
        listView.setAdapter(customAdapter);
    }
	
	// ===========================================================
	// Private Methods
	// ===========================================================

	/**
	 * ListView数据创建
	 */
	private List<String> createTestData() {
		List<String> data = new ArrayList<String>();
		for (int i = 0; i < 50; i++) {
			data.add("Love World " + i);
		}
		return data;
	}
    
	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================


	private class CustomAdapter extends BaseAdapter {

		private List<String> mData;
		private LayoutInflater mInflater;
		
		public CustomAdapter(Context context, List<String> data) {
			mData = data;
			mInflater = LayoutInflater.from(context);
		}
		
		@Override
		public int getCount() {
			if (mData == null || mData.size() <= 0) {
				return 0;
			}
			return mData.size();
		}

		@Override
		public Object getItem(int position) {
			if (mData == null || mData.size() <= 0
					|| position < 0 || position >= mData.size()) {
				return null;
			}
			return mData.get(position);
		}

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

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			if (convertView == null) {
				convertView = mInflater.inflate(R.layout.list_item, null);
			}
			
			TextView name = (TextView) convertView.findViewById(R.id.tv_name);
			name.setText( (CharSequence) getItem(position) );
			
			return convertView;
		}
		
	}
	

}

 2. Item布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/background"
    android:padding="15dip" >

    <ImageView
        android:id="@+id/iv_photo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10dip"
        android:layout_toRightOf="@id/iv_photo"
        android:textSize="16dip" />

</RelativeLayout>

3. 之前已经分析了自定义ListView需要覆写AdapterView,其中getAdapter,setAdapter,getSelectedView,getSelectedView四个抽象方法是必须要覆写的。

其中会覆写onMeasure和onLayout如果不太了解,可以查看之前的博文

《覆写onLayout进行layout,含自定义ViewGroup例子》 

《 覆写onMeaure进行measure操作》

以下是当前例子具体代码

public class ListView3D extends AdapterView<Adapter> {
	// ===========================================================
	// Constants
	// ===========================================================
	
	// 新添加的所有子视图在当前最当前最后一个子视图后添加的布局模型
	private static final int LAYOUT_MODE_BELOW = 0;
	// 与LAYOUT_MODE_BELOW相反方向添加的布局模型
    private static final int LAYOUT_MODE_ABOVE = 1;
	
	// ===========================================================
	// Fields
	// ===========================================================

	// 视图和数据适配
	private Adapter mAdapter;
	// 当前显示最后一个Item在Adapter中位置
	private int mLastItemPosition = -1;
	// 当前显示第一个Item在Adapter中位置
	private int mFirstItemPosition;
	
	
	// 当前顶部第一个item
	private int mListTop;
	private int mListTopOffset;
	
	// ===========================================================
	// Constructors
	// ===========================================================

	public ListView3D(Context context) {
		super(context);
	}

	public ListView3D(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public ListView3D(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	
	// ===========================================================
	// Public Methods
	// ===========================================================

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	@Override
	public Adapter getAdapter() {
		return mAdapter;
	}

	@Override
	public void setAdapter(Adapter adapter) {
		mAdapter = adapter;
		removeAllViewsInLayout();
		requestLayout();
	}

	@Override
	public View getSelectedView() {
		throw new UnsupportedOperationException("Not supported");
	}

	@Override
	public void setSelection(int position) {
		throw new UnsupportedOperationException("Not supported");
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		
		// 异常处理
		if (mAdapter == null) {
			return;
		}
		
		// 当前ListView没有任何子视图(Item),所以依次在从上向下填充子视图
		if (getChildCount() == 0) {
			mLastItemPosition = -1;
			// add and measure
			fillListDown(mListTop, 0);
		}

		// layout,添加测量完后,获取视图摆放位置
		positioinItems();
		
		// draw, 上面子视图都添加完了,重绘布局把子视图绘制出来吧
		invalidate();
		
	}

	
	


	// ===========================================================
	// Private Methods
	// ===========================================================


	/**
	 * 向当前最后一个子视图下面添加,填充到当前ListView底部无再可填充区域为止
	 * 
	 * @param bottomEdge 当前最后一个子视图底部边界值
	 * @param offset 显示区域偏移量
	 */
	private void fillListDown(int bottomEdge, int offset) {
		while (bottomEdge + offset < getHeight() && mLastItemPosition < mAdapter.getCount() - 1) {
			// 现在添加的视图时当前子视图后面,所以位置+1
			mLastItemPosition++;
			// 数据和视图通过Adapter适配,此处从Adapter获取视图。
			// 第二个参数传入复用的View对象,先出入null,之后再添加View对象复用机制
			View newBottomChild = mAdapter.getView(mLastItemPosition, null, this);
			// **具体添加视图处理
			addAndMeasureChild(newBottomChild, LAYOUT_MODE_BELOW);
			// 添加一个子视图(Item),随之底部边界也发生改变
			bottomEdge += newBottomChild.getMeasuredHeight();
		}
	}

	
	/**
	 * 向当前ListView添加子视图并负责Measure子视图操作
	 * 
	 * @param child  需要添加的ListView子视图(Item)  
	 * @param layoutMode  在顶部添加上面添加还是在底部下面添加子视图 , LAYOUT_MODE_ABOVE 或 LAYOUT_MODE_BELOW
	 */
	private void addAndMeasureChild(View child, int layoutMode) {
		LayoutParams params = child.getLayoutParams();
		if (params == null) {
			params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
		}

		final int index = layoutMode == LAYOUT_MODE_ABOVE ? 0: -1;
		addViewInLayout(child, index, params, true);

		final int itemWidth = getWidth();
		// 位运算 | itemWidth表示添加此当前值
		child.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.UNSPECIFIED);
	}

	
	
	/**
	 * 对所有子视图进行layout操作,取得所有子视图正确的位置
	 */
	private void positioinItems() {
		int top = mListTop + mListTopOffset;
		
		for (int i = 0; i < getChildCount(); i++) {
			final View child = getChildAt(i);
			
			// 当前视图未虽然添加到ViewGroup但是还未重新进行measure, layout, draw操作
			// 直接通过child.getWidth();获取不到宽度
			final int width = child.getMeasuredWidth();
			final int height = child.getMeasuredHeight();
			final int left = (getWidth() - width) / 2;
			
			child.layout(left, top, left + width, top + height);
			top += height;
		}
	}
	
	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================



}


四、疑问?

为什么在layout中对子视图进行add,measure,layout,draw?


五、源码下载


点击下载源码


     当前只能显示一屏幕Item,ListView不能滚动,也没有点击事件,更没有fling效果,只是一个初步的ListView模型。



转载请注明原文地址:http://blog.csdn.net/love_world_/article/details/8734255



©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页