【Android界面实现】XListView实现原理讲解及分析

转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992

XListview是一个非常受欢迎的下拉刷新控件,但是已经停止维护了。之前写过一篇XListview的使用介绍,用起来非常简单,这两天放假无聊,研究了下XListview的实现原理,学到了很多,今天分享给大家。

提前声明,为了让代码更好的理解,我对代码进行了部分删减和重构,如果大家想看原版代码,请去github自行下载。

Xlistview项目主要是三部分:XlistView,XListViewHeader,XListViewFooter,分别是XListView主体、header、footer的实现。下面我们分开来介绍。

下面是修改之后的XListViewHeader代码

public class XListViewHeader extends LinearLayout {

	private static final String HINT_NORMAL = "下拉刷新";
	private static final String HINT_READY = "松开刷新数据";
	private static final String HINT_LOADING = "正在加载...";

	// 正常状态
	public final static int STATE_NORMAL = 0;
	// 准备刷新状态,也就是箭头方向发生改变之后的状态
	public final static int STATE_READY = 1;
	// 刷新状态,箭头变成了progressBar
	public final static int STATE_REFRESHING = 2;
	// 布局容器,也就是根布局
	private LinearLayout container;
	// 箭头图片
	private ImageView mArrowImageView;
	// 刷新状态显示
	private ProgressBar mProgressBar;
	// 说明文本
	private TextView mHintTextView;
	// 记录当前的状态
	private int mState;
	// 用于改变箭头的方向的动画
	private Animation mRotateUpAnim;
	private Animation mRotateDownAnim;
	// 动画持续时间
	private final int ROTATE_ANIM_DURATION = 180;

	public XListViewHeader(Context context) {
		super(context);
		initView(context);
	}

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

	private void initView(Context context) {
		mState = STATE_NORMAL;
		// 初始情况下,设置下拉刷新view高度为0
		LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
				LayoutParams.MATCH_PARENT, 0);
		container = (LinearLayout) LayoutInflater.from(context).inflate(
				R.layout.xlistview_header, null);
		addView(container, lp);
		// 初始化控件
		mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow);
		mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview);
		mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar);
		// 初始化动画
		mRotateUpAnim = new RotateAnimation(0.0f, -180.0f,
				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
				0.5f);
		mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
		mRotateUpAnim.setFillAfter(true);
		mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f,
				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
				0.5f);
		mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
		mRotateDownAnim.setFillAfter(true);
	}

	// 设置header的状态
	public void setState(int state) {
		if (state == mState)
			return;

		// 显示进度
		if (state == STATE_REFRESHING) {
			mArrowImageView.clearAnimation();
			mArrowImageView.setVisibility(View.INVISIBLE);
			mProgressBar.setVisibility(View.VISIBLE);
		} else {
			// 显示箭头
			mArrowImageView.setVisibility(View.VISIBLE);
			mProgressBar.setVisibility(View.INVISIBLE);
		}

		switch (state) {
		case STATE_NORMAL:
			if (mState == STATE_READY) {
				mArrowImageView.startAnimation(mRotateDownAnim);
			}
			if (mState == STATE_REFRESHING) {
				mArrowImageView.clearAnimation();
			}
			mHintTextView.setText(HINT_NORMAL);
			break;
		case STATE_READY:
			if (mState != STATE_READY) {
				mArrowImageView.clearAnimation();
				mArrowImageView.startAnimation(mRotateUpAnim);
				mHintTextView.setText(HINT_READY);
			}
			break;
		case STATE_REFRESHING:
			mHintTextView.setText(HINT_LOADING);
			break;
		}

		mState = state;
	}

	public void setVisiableHeight(int height) {
		if (height < 0)
			height = 0;
		LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) container
				.getLayoutParams();
		lp.height = height;
		container.setLayoutParams(lp);
	}

	public int getVisiableHeight() {
		return container.getHeight();
	}

	public void show() {
		container.setVisibility(View.VISIBLE);
	}

	public void hide() {
		container.setVisibility(View.INVISIBLE);
	}

}

XListViewHeader继承自linearLayout,用来实现下拉刷新时的界面展示,可以分为三种状态:正常、准备刷新、正在加载。

在Linearlayout布局里面,主要有指示箭头、说明文本、圆形加载条三个控件。在构造函数中,调用了initView()进行控件的初始化操作。在添加布局文件的时候,指定高度为0,这是为了隐藏header,然后初始化动画,是为了完成箭头的旋转动作。

setState()是设置header的状态,因为header需要根据不同的状态,完成控件隐藏、显示、改变文字等操作,这个方法主要是在XListView里面调用。除此之外,还有setVisiableHeight()和getVisiableHeight(),这两个方法是为了设置和获取Header中根布局文件的高度属性,从而完成拉伸和收缩的效果,而show()和hide()则显然就是完成显示和隐藏的效果。

下面是Header的布局文件

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

    <RelativeLayout
        android:id="@+id/xlistview_header_content"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        tools:ignore="UselessParent" >

        <TextView
            android:id="@+id/xlistview_header_hint_textview"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:text="正在加载"
            android:textColor="@android:color/black"
            android:textSize="14sp" />

        <ImageView
            android:id="@+id/xlistview_header_arrow"
            android:layout_width="30dp"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/xlistview_header_hint_textview"
            android:src="@drawable/xlistview_arrow" />

        <ProgressBar
            android:id="@+id/xlistview_header_progressbar"
            style="@style/progressbar_style"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/xlistview_header_hint_textview"
            android:visibility="invisible" />
    </RelativeLayout>

</LinearLayout>

说完了Header,我们再看看Footer。Footer是为了完成加载更多功能时候的界面展示,基本思路和Header是一样的,下面是Footer的代码

public class XListViewFooter extends LinearLayout {

	// 正常状态
	public final static int STATE_NORMAL = 0;
	// 准备状态
	public final static int STATE_READY = 1;
	// 加载状态
	public final static int STATE_LOADING = 2;

	private View mContentView;
	private View mProgressBar;
	private TextView mHintView;

	public XListViewFooter(Context context) {
		super(context);
		initView(context);
	}

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

	private void initView(Context context) {

		LinearLayout moreView = (LinearLayout) LayoutInflater.from(context)
				.inflate(R.layout.xlistview_footer, null);
		addView(moreView);
		moreView.setLayoutParams(new LinearLayout.LayoutParams(
				LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));

		mContentView = moreView.findViewById(R.id.xlistview_footer_content);
		mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar);
		mHintView = (TextView) moreView
				.findViewById(R.id.xlistview_footer_hint_textview);
	}

	/**
	 * 设置当前的状态
	 *
	 * @param state
	 */
	public void setState(int state) {

		mProgressBar.setVisibility(View.INVISIBLE);
		mHintView.setVisibility(View.INVISIBLE);

		switch (state) {
		case STATE_READY:
			mHintView.setVisibility(View.VISIBLE);
			mHintView.setText(R.string.xlistview_footer_hint_ready);
			break;

		case STATE_NORMAL:
			mHintView.setVisibility(View.VISIBLE);
			mHintView.setText(R.string.xlistview_footer_hint_normal);
			break;

		case STATE_LOADING:
			mProgressBar.setVisibility(View.VISIBLE);
			break;

		}

	}

	public void setBottomMargin(int height) {
		if (height > 0) {

			LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView
					.getLayoutParams();
			lp.bottomMargin = height;
			mContentView.setLayoutParams(lp);
		}
	}

	public int getBottomMargin() {
		LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView
				.getLayoutParams();
		return lp.bottomMargin;
	}

	public void hide() {
		LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView
				.getLayoutParams();
		lp.height = 0;
		mContentView.setLayoutParams(lp);
	}

	public void show() {
		LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView
				.getLayoutParams();
		lp.height = LayoutParams.WRAP_CONTENT;
		mContentView.setLayoutParams(lp);
	}

}

从上面的代码里面,我们可以看出,footer和header的思路是一样的,只不过,footer的拉伸和显示效果不是通过高度来模拟的,而是通过设置BottomMargin来完成的。

下面是Footer的布局文件

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

    <RelativeLayout
        android:id="@+id/xlistview_footer_content"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        tools:ignore="UselessParent" >

        <ProgressBar
            android:id="@+id/xlistview_footer_progressbar"
            style="@style/progressbar_style"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_centerInParent="true"
            android:visibility="invisible" />

        <TextView
            android:id="@+id/xlistview_footer_hint_textview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="@string/xlistview_footer_hint_normal"
            android:textColor="@android:color/black"
            android:textSize="14sp" />
    </RelativeLayout>

</LinearLayout>

在了解了Header和footer之后,我们就要介绍最核心的XListView的代码实现了。

在介绍代码实现之前,我先介绍一下XListView的实现原理。

首先,一旦使用XListView,Footer和Header就已经添加到我们的ListView上面了,XListView就是通过继承ListView,然后处理了屏幕点击事件和控制滑动实现效果的。所以,如果我们的Adapter中getCount()返回的值是20,那么其实XListView里面是有20+2个item的,这个数量即使我们关闭了XListView的刷新和加载功能,也是不会变化的。Header和Footer通过addHeaderView和addFooterView添加上去之后,如果想实现下拉刷新和上拉加载功能,那么就必须有拉伸效果,所以就像上面的那样,Header是通过设置height,Footer是通过设置BottomMargin来模拟拉伸效果。那么回弹效果呢?仅仅通过设置高度或者是间隔是达不到模拟回弹效果的,因此,就需要用Scroller来实现模拟回弹效果。在说明原理之后,我们开始介绍XListView的核心实现原理。

再次提示,下面的代码经过我重构了,只是为了看起来更好的理解。

public class XListView extends ListView {

	private final static int SCROLLBACK_HEADER = 0;
	private final static int SCROLLBACK_FOOTER = 1;
	// 滑动时长
	private final static int SCROLL_DURATION = 400;
	// 加载更多的距离
	private final static int PULL_LOAD_MORE_DELTA = 100;
	// 滑动比例
	private final static float OFFSET_RADIO = 2f;
	// 记录按下点的y坐标
	private float lastY;
	// 用来回滚
	private Scroller scroller;
	private IXListViewListener mListViewListener;
	private XListViewHeader headerView;
	private RelativeLayout headerViewContent;
	// header的高度
	private int headerHeight;
	// 是否能够刷新
	private boolean enableRefresh = true;
	// 是否正在刷新
	private boolean isRefreashing = false;
	// footer
	private XListViewFooter footerView;
	// 是否可以加载更多
	private boolean enableLoadMore;
	// 是否正在加载
	private boolean isLoadingMore;
	// 是否footer准备状态
	private boolean isFooterAdd = false;
	// total list items, used to detect is at the bottom of listview.
	private int totalItemCount;
	// 记录是从header还是footer返回
	private int mScrollBack;

	private static final String TAG = "XListView";

	public XListView(Context context) {
		super(context);
		initView(context);
	}

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

	public XListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initView(context);
	}

	private void initView(Context context) {

		scroller = new Scroller(context, new DecelerateInterpolator());

		headerView = new XListViewHeader(context);
		footerView = new XListViewFooter(context);

		headerViewContent = (RelativeLayout) headerView
				.findViewById(R.id.xlistview_header_content);
		headerView.getViewTreeObserver().addOnGlobalLayoutListener(
				new OnGlobalLayoutListener() {
					@SuppressWarnings("deprecation")
					@Override
					public void onGlobalLayout() {
						headerHeight = headerViewContent.getHeight();
						getViewTreeObserver()
								.removeGlobalOnLayoutListener(this);
					}
				});
		addHeaderView(headerView);

	}

	@Override
	public void setAdapter(ListAdapter adapter) {
		// 确保footer最后添加并且只添加一次
		if (isFooterAdd == false) {
			isFooterAdd = true;
			addFooterView(footerView);
		}
		super.setAdapter(adapter);

	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {

		totalItemCount = getAdapter().getCount();
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 记录按下的坐标
			lastY = ev.getRawY();
			break;
		case MotionEvent.ACTION_MOVE:
			// 计算移动距离
			float deltaY = ev.getRawY() - lastY;
			lastY = ev.getRawY();
			// 是第一项并且标题已经显示或者是在下拉
			if (getFirstVisiblePosition() == 0
					&& (headerView.getVisiableHeight() > 0 || deltaY > 0)) {
				updateHeaderHeight(deltaY / OFFSET_RADIO);
			} else if (getLastVisiblePosition() == totalItemCount - 1
					&& (footerView.getBottomMargin() > 0 || deltaY < 0)) {
				updateFooterHeight(-deltaY / OFFSET_RADIO);
			}
			break;

		case MotionEvent.ACTION_UP:

			if (getFirstVisiblePosition() == 0) {
				if (enableRefresh
						&& headerView.getVisiableHeight() > headerHeight) {
					isRefreashing = true;
					headerView.setState(XListViewHeader.STATE_REFRESHING);
					if (mListViewListener != null) {
						mListViewListener.onRefresh();
					}
				}
				resetHeaderHeight();
			} else if (getLastVisiblePosition() == totalItemCount - 1) {
				if (enableLoadMore
						&& footerView.getBottomMargin() > PULL_LOAD_MORE_DELTA) {
					startLoadMore();
				}
				resetFooterHeight();
			}
			break;
		}
		return super.onTouchEvent(ev);
	}

	@Override
	public void computeScroll() {

		// 松手之后调用
		if (scroller.computeScrollOffset()) {

			if (mScrollBack == SCROLLBACK_HEADER) {
				headerView.setVisiableHeight(scroller.getCurrY());
			} else {
				footerView.setBottomMargin(scroller.getCurrY());
			}
			postInvalidate();
		}
		super.computeScroll();

	}

	public void setPullRefreshEnable(boolean enable) {
		enableRefresh = enable;

		if (!enableRefresh) {
			headerView.hide();
		} else {
			headerView.show();
		}
	}

	public void setPullLoadEnable(boolean enable) {
		enableLoadMore = enable;
		if (!enableLoadMore) {
			footerView.hide();
			footerView.setOnClickListener(null);
		} else {
			isLoadingMore = false;
			footerView.show();
			footerView.setState(XListViewFooter.STATE_NORMAL);
			footerView.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					startLoadMore();
				}
			});
		}
	}

	public void stopRefresh() {
		if (isRefreashing == true) {
			isRefreashing = false;
			resetHeaderHeight();
		}
	}

	public void stopLoadMore() {
		if (isLoadingMore == true) {
			isLoadingMore = false;
			footerView.setState(XListViewFooter.STATE_NORMAL);
		}
	}

	private void updateHeaderHeight(float delta) {
		headerView.setVisiableHeight((int) delta
				+ headerView.getVisiableHeight());
		// 未处于刷新状态,更新箭头
		if (enableRefresh && !isRefreashing) {
			if (headerView.getVisiableHeight() > headerHeight) {
				headerView.setState(XListViewHeader.STATE_READY);
			} else {
				headerView.setState(XListViewHeader.STATE_NORMAL);
			}
		}

	}

	private void resetHeaderHeight() {
		// 当前的可见高度
		int height = headerView.getVisiableHeight();
		// 如果正在刷新并且高度没有完全展示
		if ((isRefreashing && height <= headerHeight) || (height == 0)) {
			return;
		}
		// 默认会回滚到header的位置
		int finalHeight = 0;
		// 如果是正在刷新状态,则回滚到header的高度
		if (isRefreashing && height > headerHeight) {
			finalHeight = headerHeight;
		}
		mScrollBack = SCROLLBACK_HEADER;
		// 回滚到指定位置
		scroller.startScroll(0, height, 0, finalHeight - height,
				SCROLL_DURATION);
		// 触发computeScroll
		invalidate();
	}

	private void updateFooterHeight(float delta) {
		int height = footerView.getBottomMargin() + (int) delta;
		if (enableLoadMore && !isLoadingMore) {
			if (height > PULL_LOAD_MORE_DELTA) {
				footerView.setState(XListViewFooter.STATE_READY);
			} else {
				footerView.setState(XListViewFooter.STATE_NORMAL);
			}
		}
		footerView.setBottomMargin(height);

	}

	private void resetFooterHeight() {
		int bottomMargin = footerView.getBottomMargin();
		if (bottomMargin > 0) {
			mScrollBack = SCROLLBACK_FOOTER;
			scroller.startScroll(0, bottomMargin, 0, -bottomMargin,
					SCROLL_DURATION);
			invalidate();
		}
	}

	private void startLoadMore() {
		isLoadingMore = true;
		footerView.setState(XListViewFooter.STATE_LOADING);
		if (mListViewListener != null) {
			mListViewListener.onLoadMore();
		}
	}

	public void setXListViewListener(IXListViewListener l) {
		mListViewListener = l;
	}

	public interface IXListViewListener {

		public void onRefresh();

		public void onLoadMore();
	}
}

在三个构造函数中,都调用initView进行了header和footer的初始化,并且定义了一个Scroller,并传入了一个减速的插值器,为了模仿回弹效果。在initView方法里面,因为header可能还没初始化完毕,所以通过GlobalLayoutlistener来获取了header的高度,然后addHeaderView添加到了listview上面。

通过重写setAdapter方法,保证Footer最后天假,并且只添加一次。

最重要的,要属onTouchEvent了。在方法开始之前,通过getAdapter().getCount()获取到了item的总数,便于计算位置。这个操作在源代码中是通过scrollerListener完成的,因为ScrollerListener在这里没大有用,所以我直接去掉了,然后把位置改到了这里。如果在setAdapter里面获取的话,只能获取到没有header和footer的item数量。

在ACTION_DOWN里面,进行了lastY的初始化,lastY是为了判断移动方向的,因为在ACTION_MOVE里面,通过ev.getRawY()-lastY可以计算出手指的移动趋势,如果>0,那么就是向下滑动,反之向上。getRowY()是获取元Y坐标,意思就是和Window和View坐标没有关系的坐标,代表在屏幕上的绝对位置。然后在下面的代码里面,如果第一项可见并且header的可见高度>0或者是向下滑动,就说明用户在向下拉动或者是向上拉动header,也就是指示箭头显示的时候的状态,这时候调用了updateHeaderHeight,来更新header的高度,实现header可以跟随手指动作上下移动。这里有个OFFSET_RADIO,这个值是一个移动比例,就是说,你手指在Y方向上移动400px,如果比例是2,那么屏幕上的控件移动就是400px/2=200px,可以通过这个值来控制用户的滑动体验。下面的关于footer的判断与此类似,不再赘述。

当用户移开手指之后,ACTION_UP方法就会被调用。在这里面,只对可见位置是0和item总数-1的位置进行了处理,其实正好对应header和footer。如果位置是0,并且可以刷新,然后当前的header可见高度>原始高度的话,就说明用户确实是要进行刷新操作,所以通过setState改变header的状态,如果有监听器的话,就调用onRefresh方法,然后调用resetHeaderHeight初始化header的状态,因为footer的操作如出一辙,所以不再赘述。但是在footer中有一个PULL_LOAD_MORE_DELTA,这个值是加载更多触发条件的临界值,只有footer的间隔超过这个值之后,才能够触发加载更多的功能,因此我们可以修改这个值来改变用户体验。

说到现在,大家应该明白基本的原理了,其实XListView就是通过对用户手势的方向和距离的判断,来动态的改变Header和Footer实现的功能,所以如果我们也有类似的需求,就可以参照这种思路进行自定义。

下面再说几个比较重要的方法。

前面我们说道,在ACTION_MOVE里面,会不断的调用下面的updateXXXX方法,来动态的改变header和fooer的状态,

private void updateHeaderHeight(float delta) {
		headerView.setVisiableHeight((int) delta
				+ headerView.getVisiableHeight());
		// 未处于刷新状态,更新箭头
		if (enableRefresh && !isRefreashing) {
			if (headerView.getVisiableHeight() > headerHeight) {
				headerView.setState(XListViewHeader.STATE_READY);
			} else {
				headerView.setState(XListViewHeader.STATE_NORMAL);
			}
		}

	}

private void updateFooterHeight(float delta) {
		int height = footerView.getBottomMargin() + (int) delta;
		if (enableLoadMore && !isLoadingMore) {
			if (height > PULL_LOAD_MORE_DELTA) {
				footerView.setState(XListViewFooter.STATE_READY);
			} else {
				footerView.setState(XListViewFooter.STATE_NORMAL);
			}
		}
		footerView.setBottomMargin(height);

	}

在移开手指之后,会调用下面的resetXXX来初始化header和footer的状态

private void resetHeaderHeight() {
		// 当前的可见高度
		int height = headerView.getVisiableHeight();
		// 如果正在刷新并且高度没有完全展示
		if ((isRefreashing && height <= headerHeight) || (height == 0)) {
			return;
		}
		// 默认会回滚到header的位置
		int finalHeight = 0;
		// 如果是正在刷新状态,则回滚到header的高度
		if (isRefreashing && height > headerHeight) {
			finalHeight = headerHeight;
		}
		mScrollBack = SCROLLBACK_HEADER;
		// 回滚到指定位置
		scroller.startScroll(0, height, 0, finalHeight - height,
				SCROLL_DURATION);
		// 触发computeScroll
		invalidate();
	}

private void resetFooterHeight() {
		int bottomMargin = footerView.getBottomMargin();
		if (bottomMargin > 0) {
			mScrollBack = SCROLLBACK_FOOTER;
			scroller.startScroll(0, bottomMargin, 0, -bottomMargin,
					SCROLL_DURATION);
			invalidate();
		}
	}

我们可以看到,滚动操作不是通过直接的设置高度来实现的,而是通过Scroller.startScroll()来实现的,通过调用此方法,computeScroll()就会被调用,然后在这个里面,根据mScrollBack区分是哪一个滚动,然后再通过设置高度和间隔,就可以完成收缩的效果了。
    至此,整个XListView的实现原理就完全的搞明白了,以后如果做滚动类的自定义控件,应该也有思路了。

谢谢观看,休息一下。。。

时间: 2024-10-09 11:23:08

【Android界面实现】XListView实现原理讲解及分析的相关文章

Android XListView实现原理讲解及分析

XListview是一个非常受欢迎的下拉刷新控件,但是已经停止维护了.之前写过一篇XListview的使用介绍,用起来非常简单,这两天放假无聊,研究了下XListview的实现原理,学到了很多,今天分享给大家. 提前声明,为了让代码更好的理解,我对代码进行了部分删减和重构,如果大家想看原版代码,请去github自行下载. Xlistview项目主要是三部分:XlistView,XListViewHeader,XListViewFooter,分别是XListView主体.header.footer

【Android 界面效果45】ViewPager源码分析

ViewPager概述: Layout manager that allows the user to flip left and right through pages of data. You supply an implementation of a PagerAdapter to generate the pages that the view shows. Note this class is currently under early design and development.

iOS中xib与storyboard原理,与Android界面布局的异同

用文本标记语言来进行布局,用的最多的应该是HTML语言.HTML可以理解为有一组特殊标记的XML语言. 一.iOS中xib与storyboard显示原理 在iOS中主要的布置界面的方式有3种:代码,xib,storyboard. 1. 代码 代码布置界面是万能的,但通常很复杂.布置一个简单的界面可能需要很多行代码,因此十分繁琐. 下面为创建一个按钮的代码,最少也要3行: UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd

Android界面编程——导航栏及菜单(六)

Android界面编程--导航栏及菜单 2.7导航栏及菜单 2.7.1  ActionBar ActionBar是Android3.0(API 11)开始增加的新特性,ActionBar出现在活动窗口的顶部,可以显示标题.icon.Actions按钮.可交互View,可实现应用程序级的导航,如图2.7-1所示 图2.7-1 其中 1. App icon: 主要用于展示App的Logo,如果当前界面不是一级界面,还可以展示返回航. 2.View Control: 用于切换不同的视图或者展示非交互信

Android反射机制实现与原理

本文介绍Android反射机制实现与原理,在介绍之前,要和Java进行比较,所以先看下Java中的反射相关知识: 一.反射的概念及在Java中的类反射 反射主要是指程序可以访问.检测和修改它本身状态或行为的一种能力.在计算机科学领域,反射是一类应用,它们能够自描述和自控制.这类应用通过某种机制来实现对自己行为的描述和检测,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 在Java中的反射机制,被称为Reflection(大家看到这个单词,第一个想法应该就是去开发文档中

Android界面性能调优手册

转载:https://androidtest.org/android-graphics-performance-pattens/#11 界面是 Android 应用中直接影响用户体验最关键的部分.如果代码实现得不好,界面容易发生卡顿且导致应用占用大量内存. 我司这类做 ROM 的公司更不一样,预装的应用一定要非常流畅,这样给客户或用户的第一感觉就是快.又卡又慢的应用体验,会影响客户或用户对产品的信心和评价,所以不可忽视. 目录 一. Android渲染知识 1.1 绘制原理 1.2 掉帧 1.3

Android界面编程——Android基本控件

 Android界面编程 Android应用开发的一项重要内容就是界面开发.对于用户来说,不管APP包含的逻辑多么复杂,功能多么强大,如果没有提供友好的图形交互界面,将很难吸引最终用户. 作为一个程序员如何才能开发出友好的图形界面呢.实际上Android提供了非常丰富UI(User Interface)控件,开发者只要掌握了这些控件的特性,按照一定的规律,就可以像堆积木一样开发出友好的图形界面. 本章内容将介绍常用控件的具体用法. 2.1  Android UI的基础知识 Android中所有的

Android界面编程——Android高级UI组件(三)

Android界面编程 Android高级UI组件 2.4.1适配器组件 适配器的作用 适配器充当适配器控件和该视图数据之间的桥梁.适配器提供访问的数据项,并负责产生数据组中的每个项的视图. 常用的适配器 BaseAdapter:抽象类,具有较高的灵活性. ArrayAdapter:最为简单,智能展示一行文字. SimpleAdapter:有较好的扩充性,可以自定义出各种效果. SimpleCursorAdapter:主要用于操作数据库. 常用的适配器控制 适配器控件扩展自ViewAdapter

Android界面编程——Android布局组件(二)

Android界面编程 2.3.1 布局介绍 布局用于定义Activity中UI元素的排列结构,Android提供了LinearLayout线性布局.RelativeLayout相对布局 .FrameLayout帧布局 .TableLayout表格布局.AbsoluteLayout坐标布局 共五种布局,可以通过两种方式声明布局: ? 在 XML 中声明 UI 元素.Android 提供了对应于 View 类及其子类的XML 元素 ? 运行时实例化布局元素.可以通过编程创建 View 对象和 Vi