前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >XListView实现下拉刷新和上拉加载原理解析

XListView实现下拉刷新和上拉加载原理解析

作者头像
砸漏
发布2020-10-26 22:25:40
7650
发布2020-10-26 22:25:40
举报
文章被收录于专栏:恩蓝脚本恩蓝脚本

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

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

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

下面是修改之后的XListViewHeader代码

代码语言:javascript
复制
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的布局文件

代码语言:javascript
复制
<?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的代码

代码语言:javascript
复制
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的布局文件

代码语言:javascript
复制
<?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的核心实现原理。 再次提示,下面的代码经过我重构了,只是为了看起来更好的理解。

代码语言:javascript
复制
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的状态,

代码语言:javascript
复制
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的状态

代码语言:javascript
复制
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的实现原理就完全的搞明白了,以后如果做滚动类的自定义控件,应该也有思路了。

以上就是本文的全部内容,希望对大家的学习有所帮助。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档