Android 关于Scrollview和listview嵌套以及下拉刷新问题(附源码)
2015-12-01 15:18
441 查看
本人Android菜鸟,在做东西时候发现有时候我们需要在项目中使用scrollview和listview的结合才能使项目看起来更加完善,但是谷歌官网是不推荐scrollview和listview一起嵌套的,因为这俩个东西嵌套后会出现很多事件冲突。但是有时候就是需要这样的搭配怎么办?所以有些牛人自定义了view来满足这样的需求。好了,废话不多说,上代码
项目只需将PullDownScrollView.java这个类包裹scrollview就行,然后个给scrollview添加一个属性
然后你就可以布置自己的布局,但是当你加入listview时,你会发现你的listview只会显示几行(本博主显示俩行),然后我查了一些资料,说需要重新定义listview的高度,但是博主觉得重新定义高度有点麻烦,就想着既然都自定义视图了,干脆在定义listview不就得了,代码如下
随后就是下拉刷新了,在MainActivity中获取到PullDownScrollView,实现其方法,代码如下
就是当你手触摸屏幕时会触发焦点事件,然后博主灵光一闪,禁用listview的焦点是不是listview就不会得到事件了。当博主抱着试一试的态度设置了
如果有大神发现有什么不足之处希望指出,或者有什么更好的办法解决类似以下需求的方法,
<scrollview>
<一些其他布局,需要动态刷新数据(如动态广告或者一些服务器更新了的内容)/>
<listview></listview>
</scroolview>
附上源码下载地址点击打开链接http://download.csdn.net/detail/pengguichu/9316271
package com.example.pulldown; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.LinearLayout; import android.widget.ScrollView; /** * * @author pengguichu * */ public class PullDownScrollView extends LinearLayout { private static final String TAG = "PullDownScrollView"; private int refreshTargetTop = -60; private int headContentHeight; private RefreshListener refreshListener; private RotateAnimation animation; private RotateAnimation reverseAnimation; private final static int RATIO = 2; private int preY = 0; private boolean isElastic = false; private int startY; private int state; private String note_release_to_refresh = "松开更新"; private String note_pull_to_refresh = "下拉刷新"; private String note_refreshing = "正在更新..."; private IPullDownElastic mElastic; public PullDownScrollView(Context context) { super(context); init(); } public PullDownScrollView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator()); animation.setDuration(250); animation.setFillAfter(true); reverseAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); reverseAnimation.setInterpolator(new LinearInterpolator()); reverseAnimation.setDuration(200); reverseAnimation.setFillAfter(true); } /** * 刷新监听 * @param listener */ public void setRefreshListener(RefreshListener listener) { this.refreshListener = listener; } /** * 下拉布局 * @param elastic */ public void setPullDownElastic(IPullDownElastic elastic) { mElastic = elastic; headContentHeight = mElastic.getElasticHeight(); refreshTargetTop = - headContentHeight; LayoutParams lp = new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, headContentHeight); lp.topMargin = refreshTargetTop; addView(mElastic.getElasticLayout(), 0, lp); } /** * 设置更新提示语 * @param pullToRefresh 下拉刷新提示语 * @param releaseToRefresh 松开刷新提示语 * @param refreshing 正在刷新提示语 */ public void setRefreshTips(String pullToRefresh, String releaseToRefresh, String refreshing) { note_pull_to_refresh = pullToRefresh; note_release_to_refresh = releaseToRefresh; note_refreshing = refreshing; } /* * 该方法一般和ontouchEvent 一起用 (non-Javadoc) * * @see * android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent) */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Logger.d(TAG, "onInterceptTouchEvent"); printMotionEvent(ev); if (ev.getAction() == MotionEvent.ACTION_DOWN) { preY = (int) ev.getY(); } if (ev.getAction() == MotionEvent.ACTION_MOVE) { Logger.d(TAG, "isElastic:" + isElastic + " canScroll:"+ canScroll() + " ev.getY() - preY:"+(ev.getY() - preY)); if (!isElastic && canScroll() && (int) ev.getY() - preY >= headContentHeight / (3*RATIO) && refreshListener != null && mElastic != null) { isElastic = true; startY = (int) ev.getY(); Logger.i(TAG, "在move时候记录下位置startY:" + startY); return true; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Logger.d(TAG, "onTouchEvent"); printMotionEvent(event); handleHeadElastic(event); return super.onTouchEvent(event); } private void handleHeadElastic(MotionEvent event) { if (refreshListener != null && mElastic != null) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Logger.i(TAG, "down"); break; case MotionEvent.ACTION_UP: Logger.i(TAG, "up"); if (state != IPullDownElastic.REFRESHING && isElastic) { if (state == IPullDownElastic.DONE) { // 什么都不做 setMargin(refreshTargetTop); } if (state == IPullDownElastic.PULL_To_REFRESH) { state = IPullDownElastic.DONE; setMargin(refreshTargetTop); changeHeaderViewByState(state, false); Logger.i(TAG, "由下拉刷新状态,到done状态"); } if (state == IPullDownElastic.RELEASE_To_REFRESH) { state = IPullDownElastic.REFRESHING; setMargin(0); changeHeaderViewByState(state, false); onRefresh(); Logger.i(TAG, "由松开刷新状态,到done状态"); } } isElastic = false; break; case MotionEvent.ACTION_MOVE: Logger.i(TAG, "move"); int tempY = (int) event.getY(); if (state != IPullDownElastic.REFRESHING && isElastic) { // 可以松手去刷新了 if (state == IPullDownElastic.RELEASE_To_REFRESH) { if (((tempY - startY) / RATIO < headContentHeight) && (tempY - startY) > 0) { state = IPullDownElastic.PULL_To_REFRESH; changeHeaderViewByState(state, true); Logger.i(TAG, "由松开刷新状态转变到下拉刷新状态"); } else if (tempY - startY <= 0) { state = IPullDownElastic.DONE; changeHeaderViewByState(state, false); Logger.i(TAG, "由松开刷新状态转变到done状态"); } } if (state == IPullDownElastic.DONE) { if (tempY - startY > 0) { state = IPullDownElastic.PULL_To_REFRESH; changeHeaderViewByState(state, false); } } if (state == IPullDownElastic.PULL_To_REFRESH) { // 下拉到可以进入RELEASE_TO_REFRESH的状态 if ((tempY - startY) / RATIO >= headContentHeight) { state = IPullDownElastic.RELEASE_To_REFRESH; changeHeaderViewByState(state, false); Logger.i(TAG, "由done或者下拉刷新状态转变到松开刷新"); } else if (tempY - startY <= 0) { state = IPullDownElastic.DONE; changeHeaderViewByState(state, false); Logger.i(TAG, "由DOne或者下拉刷新状态转变到done状态"); } } if (tempY - startY > 0) { setMargin((tempY - startY)/2 + refreshTargetTop); } } break; } } } /** * */ private void setMargin(int top) { LinearLayout.LayoutParams lp = (LayoutParams) mElastic.getElasticLayout() .getLayoutParams(); lp.topMargin = top; // 修改后刷新 mElastic.getElasticLayout().setLayoutParams(lp); mElastic.getElasticLayout().invalidate(); } private void changeHeaderViewByState(int state, boolean isBack) { mElastic.changeElasticState(state, isBack); switch (state) { case IPullDownElastic.RELEASE_To_REFRESH: mElastic.showArrow(View.VISIBLE); mElastic.showProgressBar(View.GONE); mElastic.showLastUpdate(View.VISIBLE); mElastic.setTips(note_release_to_refresh); mElastic.clearAnimation(); mElastic.startAnimation(animation); Logger.i(TAG, "当前状态,松开刷新"); break; case IPullDownElastic.PULL_To_REFRESH: mElastic.showArrow(View.VISIBLE); mElastic.showProgressBar(View.GONE); mElastic.showLastUpdate(View.VISIBLE); mElastic.setTips(note_pull_to_refresh); mElastic.clearAnimation(); // 是由RELEASE_To_REFRESH状态转变来的 if (isBack) { mElastic.startAnimation(reverseAnimation); } Logger.i(TAG, "当前状态,下拉刷新"); break; case IPullDownElastic.REFRESHING: mElastic.showArrow(View.GONE); mElastic.showProgressBar(View.VISIBLE); mElastic.showLastUpdate(View.GONE); mElastic.setTips(note_refreshing); mElastic.clearAnimation(); Logger.i(TAG, "当前状态,正在刷新..."); break; case IPullDownElastic.DONE: mElastic.showProgressBar(View.GONE); mElastic.clearAnimation(); // arrowImageView.setImageResource(R.drawable.goicon); // tipsTextview.setText("下拉刷新"); // lastUpdatedTextView.setVisibility(View.VISIBLE); Logger.i(TAG, "当前状态,done"); break; } } private void onRefresh() { // downTextView.setVisibility(View.GONE); // scroller.startScroll(0, i, 0, 0 - i); // invalidate(); if (refreshListener != null) { refreshListener.onRefresh(this); } } /** * */ @Override public void computeScroll() { // if (scroller.computeScrollOffset()) { // int i = this.scroller.getCurrY(); // LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) this.refreshView // .getLayoutParams(); // int k = Math.max(i, refreshTargetTop); // lp.topMargin = k; // this.refreshView.setLayoutParams(lp); // this.refreshView.invalidate(); // invalidate(); // } } /** * 结束刷新事件,UI刷新完成后必须回调此方法 * @param text 一般传入:“上次更新时间:12:23” */ public void finishRefresh(String text) { if (mElastic == null) { Logger.e(TAG, "finishRefresh mElastic:" + mElastic); return; } if (state == IPullDownElastic.DONE) { Logger.e(TAG, "==> finishRefresh state has already done"); } state = IPullDownElastic.DONE; if (text != null) { mElastic.setLastUpdateText(text); } changeHeaderViewByState(state,false); Logger.i(TAG, "==>执行了=====finishRefresh "+ text); mElastic.showArrow(View.VISIBLE); mElastic.showLastUpdate(View.VISIBLE); setMargin(refreshTargetTop); // scroller.startScroll(0, i, 0, refreshTargetTop); // invalidate(); } private boolean canScroll() { View childView; if (getChildCount() > 1) { childView = this.getChildAt(1); if (childView instanceof AbsListView) { int top = ((AbsListView) childView).getChildAt(0).getTop(); int pad = ((AbsListView) childView).getListPaddingTop(); if ((Math.abs(top - pad)) < 3 && ((AbsListView) childView).getFirstVisiblePosition() == 0) { return true; } else { return false; } } else if (childView instanceof ScrollView) { if (((ScrollView) childView).getScrollY() == 0) { return true; } else { return false; } } } return canScroll(this); } /** * 子类重写此方法可以兼容其它的子控件,目前只兼容AbsListView和ScrollView * @param view * @return */ public boolean canScroll(PullDownScrollView view) { return false; } private void printMotionEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Logger.d(TAG, "down"); break; case MotionEvent.ACTION_MOVE: Logger.d(TAG, "move"); break; case MotionEvent.ACTION_UP: Logger.d(TAG, "up"); default: break; } } /** * 刷新监听接口 */ public interface RefreshListener { public void onRefresh(PullDownScrollView view); } }既然官网不支持scrollview嵌套listview,那么我们就使用其他的View来代替scrollview,所以我参照了网上的一些代码,改了一些东西,重写的LinearLayout,将他的触摸事件改成了类似于scrollview,方法给了注释,想要研究的可以看看。
项目只需将PullDownScrollView.java这个类包裹scrollview就行,然后个给scrollview添加一个属性
android:scrollbars="none"就可以了
然后你就可以布置自己的布局,但是当你加入listview时,你会发现你的listview只会显示几行(本博主显示俩行),然后我查了一些资料,说需要重新定义listview的高度,但是博主觉得重新定义高度有点麻烦,就想着既然都自定义视图了,干脆在定义listview不就得了,代码如下
import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View.MeasureSpec; import android.widget.ListView; /** * * 不能在一个拥有Scrollbar的组件中嵌入另一个拥有Scrollbar的组件,因为这不科学,会混淆滑动事件,导致只显示一到两行数据。那么就换一种思路, * 首先让子控件的内容全部显示出来,禁用了它的滚动。如果超过了父控件的范围则显示父控件的scrollbar滚动显示内容,思路是这样,一下是代码。 * 重载onMeasure方法: * * @author pengguichu * */ public class MyListView extends ListView { public MyListView(Context context, AttributeSet attrs) { super(context, attrs); } public MyListView(Context context) { super(context); } public MyListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /*** * * 改变高度 其中onMeasure函数决定了组件显示的高度与宽度; * makeMeasureSpec函数中第一个函数决定布局空间的大小,第二个参数是布局模式 * MeasureSpec.AT_MOST的意思就是子控件需要多大的控件就扩展到多大的空间 * 之后在ScrollView中添加这个组件就OK了,同样的道理,ListView也适用。 */ @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } }代码很简单,只是继承了listview,重写了listview的onMeasure方法,让其先计算高度,再显示内容,将他替换掉你的listview,这样你的listview的内容就能全部显示出来了。
随后就是下拉刷新了,在MainActivity中获取到PullDownScrollView,实现其方法,代码如下
mPullDownScrollView = (PullDownScrollView) findViewById(R.id.refresh_root);设置监听
mPullDownScrollView.setRefreshListener(this); mPullDownScrollView.setPullDownElastic(new PullDownElasticImp(this));然后实现为实现的方法
@Override public void onRefresh(PullDownScrollView view) { new Handler().postDelayed(new Runnable() { @Override public void run() { // TODO Auto-generated method stub mPullDownScrollView.finishRefresh("上次刷新时间:12:23"); } }, 2000); }那么下拉刷新就搞定了。但是此时你会发现,每当你进入界面或者下拉的时候,他会自动定义到listview的顶端,这样当你scrollview和listview中间还有一些其他的布局时,这样用户体验很不好,于是我就想,进入的时候我手动把scrollview滚到顶部。这样是解决了进入时会自动滚到listview顶部,但是,当你下拉时发现,卧槽,它还是会滚动到listview的顶部,手动滚动的方法不管用了。这个问题困扰了博主很久,问了很多人,都不能给出很明确的回答,于是我查看了文档,发现listview和scrollview都会有焦点。
就是当你手触摸屏幕时会触发焦点事件,然后博主灵光一闪,禁用listview的焦点是不是listview就不会得到事件了。当博主抱着试一试的态度设置了
listView.setFocusable(false);发现下拉刷新不会自己滚动到listview的顶部了,一切都归于平静。
如果有大神发现有什么不足之处希望指出,或者有什么更好的办法解决类似以下需求的方法,
<scrollview>
<一些其他布局,需要动态刷新数据(如动态广告或者一些服务器更新了的内容)/>
<listview></listview>
</scroolview>
附上源码下载地址点击打开链接http://download.csdn.net/detail/pengguichu/9316271
相关文章推荐
- 如何把函数写短
- android多渠道打包工具
- Android Studio之导入Project(项目)
- 简单讲解Android开发中触摸和点击事件的相关编程方法
- Android第三方开源对话消息提示框:SweetAlertDialog(sweet-alert-dialog)
- Android事件分发机制浅析
- android Dialog去掉黑色的背景和边框
- Android应用开发--MP3音乐播放器代码实现(一)
- Android第三方开源对话消息提示框:SweetAlertDialog(sweet-alert-dialog)
- Android String Placeholders
- Android支付之支付宝支付(一)
- Android 5.1 Dialog 溢出
- AndroidManifest.xml 配置文件
- 安卓扫码:简单的ZXing使用记录
- Android开发中Socket通信的基本实现方法讲解
- Android编程实现设置按钮背景透明与半透明及图片背景透明的方法
- android图表收益曲线-MPAndroidChart
- Android快捷方式解密
- Android编程实现TextView字体颜色设置的方法小结
- Android获取图片任意一点的RGB值