Android Scrollview+Listview 实现不同条目点赞并计数功能及原理分析(1)
2015-10-28 22:40
711 查看
兑现我的承诺,开始了Android的学习,实现的demo目的很明确,就是对listview里面的内容点赞,然后赞的数目改变。
我先说一下将会解决的问题,没准你也遇到了
Listview在Scrollview中无法显示完全
子view中的button等劫持了listview中item的clickListener
OnClickListener接口的onClick方法中的View view参数究竟是什么
点赞后计数改变
每个问题都是常见问题,但是我想好好说说,所以分了三篇博客,各说一个问题。
先说Listview在Scrollview中无法显示完全。
为什么listview显示不全,因为在Scrollview在measure时计算不出Listview的大小,所以只能显示一行。
View,也就是我们的具体每一个图案,View在绘图时首先measure之后layout最后draw,简单说就是先计算大小,之后计算布局,最后就是绘画了。我们这部分涉及的是measure。View 的measure是从root开始一级一级计算自己的child,listview在Scrollview中就是他的子view。
这个问题无论是从哪里出发,解决问题的关键点都是得到ListView的高度。然后Scrollview在调用子view的measure方法就可以得到具体高度
这个问题网上很多解释,最多的是openstack中的一位大神的解释,我看过很多demo都是这种解决方式。写个工具类,然后再listview.set(adapter)之后写上Utility.setListViewHeightBasedOnChildren(listview)
这个方法确实可以解决一部分人的问题,但是有博客说,针对中文可能会有bug,于是上面的方法需要改写一下
可以发现主要改动的是measure方法的参数,能够显示完全了,但是还是有bug,就是感觉Scrollview滑到没有物体地方了。
所以继续查,第三种方法是写mylistview继承Listview,重写onMeasure方法。
这种方法可以完美的解决问题。好了先解决了问题,再来思考一下怎么回事。第一种方法就可以看出了他的思路就是计算这个listview到底有多高。
那我们看看源码
measure是final的,不能被子类修改,但是真正的计算view大小是onMeasure方法,这个方法是可以被子类继承并修改,也是我们经常做的。继续追下去
这里我们就要看以下为什么是listItem.measure(0, 0);
首先执行measure方法到onMeasure(0,0),这里会有几行代码
关于这个再看一下这个代码
简单来说就是measure穿进去的值是一个int,这个32位的数字0-29位是他的size(大小)高2位是mode(模式)
UNSPECIFIED -0
EXACTLY-1
AT_MOST-2
这三个mode在View的计算中常用,但是有点走远了,有兴趣去看看源码。所以listview.measure(0,0)就是传进去了UNSPECIFIED size为0。之后就要计算高度
measure之后才能调用listItem.getMeasuredHeight(),之前调用都会是0,现在就有了整个listview所有listItem的高度,最后加上item中间的缝隙就是最后的高度
至于第二种方法我弄不清楚是为什么。看样子应该是中文对listItem的宽度有一些影响。
接下来看最靠谱的解决方法,主要就是重写了onMeasure方法
根据刚才的分析,这里是设置一个高度,模式是AT_MOST,size是最大的Integer左移2位的数字。
之后计算这个ListView的高度
可以发现不同的MODE的计算方法是不一样的。继续追
上面的方法就将会计算所有的ListView长度。最后只要super.onMeasure(widthMeasureSpec, expandSpec);就可以计算出ListVIew 的高度了。
第一个问题就解决了,下一篇博客继续解决问题,最后将会给出demo代码,需要的孩子可以好好研究。
我先说一下将会解决的问题,没准你也遇到了
Listview在Scrollview中无法显示完全
子view中的button等劫持了listview中item的clickListener
OnClickListener接口的onClick方法中的View view参数究竟是什么
点赞后计数改变
每个问题都是常见问题,但是我想好好说说,所以分了三篇博客,各说一个问题。
先说Listview在Scrollview中无法显示完全。
为什么listview显示不全,因为在Scrollview在measure时计算不出Listview的大小,所以只能显示一行。
View,也就是我们的具体每一个图案,View在绘图时首先measure之后layout最后draw,简单说就是先计算大小,之后计算布局,最后就是绘画了。我们这部分涉及的是measure。View 的measure是从root开始一级一级计算自己的child,listview在Scrollview中就是他的子view。
这个问题无论是从哪里出发,解决问题的关键点都是得到ListView的高度。然后Scrollview在调用子view的measure方法就可以得到具体高度
这个问题网上很多解释,最多的是openstack中的一位大神的解释,我看过很多demo都是这种解决方式。写个工具类,然后再listview.set(adapter)之后写上Utility.setListViewHeightBasedOnChildren(listview)
public class Utility { public static void setListViewHeightBasedOnChildren(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int totalHeight = 0; for (int i = 0, len = listAdapter.getCount(); i < len; i++) { View listItem = listAdapter.getView(i, null, listView); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); listView.setLayoutParams(params); }
这个方法确实可以解决一部分人的问题,但是有博客说,针对中文可能会有bug,于是上面的方法需要改写一下
public static void setListViewHeightBasedOnChildrenNew(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { return; } int totalHeight = 0; for (int i = 0, len = listAdapter.getCount(); i < len; i++) { View listItem = listAdapter.getView(i, null, listView); int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.AT_MOST); listItem.measure(desiredWidth, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); listView.setLayoutParams(params); }
可以发现主要改动的是measure方法的参数,能够显示完全了,但是还是有bug,就是感觉Scrollview滑到没有物体地方了。
所以继续查,第三种方法是写mylistview继承Listview,重写onMeasure方法。
public class MyListView extends ListView { public ScrollViewWithListView(Context context, AttributeSet attrs) { super(context, attrs); } public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } }
这种方法可以完美的解决问题。好了先解决了问题,再来思考一下怎么回事。第一种方法就可以看出了他的思路就是计算这个listview到底有多高。
那我们看看源码
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { mPrivateFlags &= ~MEASURED_DIMENSION_SET; if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE); } onMeasure(widthMeasureSpec, heightMeasureSpec); if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; }
measure是final的,不能被子类修改,但是真正的计算view大小是onMeasure方法,这个方法是可以被子类继承并修改,也是我们经常做的。继续追下去
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Sets up mListPadding super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int childWidth = 0; int childHeight = 0; int childState = 0; mItemCount = mAdapter == null ? 0 : mAdapter.getCount(); if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)) { final View child = obtainView(0, mIsScrap); // Lay out child directly against the parent measure spec so that // we can obtain exected minimum width and height. measureScrapChild(child, 0, widthMeasureSpec, heightSize); childWidth = child.getMeasuredWidth(); childHeight = child.getMeasuredHeight(); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (recycleOnMeasure() && mRecycler.shouldRecycleViewType( ((LayoutParams) child.getLayoutParams()).viewType)) { mRecycler.addScrapView(child, 0); } } if (widthMode == MeasureSpec.UNSPECIFIED) { widthSize = mListPadding.left + mListPadding.right + childWidth + getVerticalScrollbarWidth(); } else { widthSize |= (childState & MEASURED_STATE_MASK); } if (heightMode == MeasureSpec.UNSPECIFIED) { heightSize = mListPadding.top + mListPadding.bottom + childHeight + getVerticalFadingEdgeLength() * 2; } if (heightMode == MeasureSpec.AT_MOST) { // TODO: after first layout we should maybe start at the first visible position, not 0 heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1); } setMeasuredDimension(widthSize, heightSize); mWidthMeasureSpec = widthMeasureSpec; }
这里我们就要看以下为什么是listItem.measure(0, 0);
首先执行measure方法到onMeasure(0,0),这里会有几行代码
final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec);
关于这个再看一下这个代码
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT; /** * Creates a measure specification based on the supplied size and mode. * * The mode must always be one of the following: * <ul> * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> * </ul> * * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's * implementation was such that the order of arguments did not matter * and overflow in either value could impact the resulting MeasureSpec. * {@link android.widget.RelativeLayout} was affected by this bug. * Apps targeting API levels greater than 17 will get the fixed, more strict * behavior.</p> * * @param size the size of the measure specification * @param mode the mode of the measure specification * @return the measure specification based on size and mode */ public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } /** * Extracts the mode from the supplied measure specification. * * @param measureSpec the measure specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, * {@link android.view.View.MeasureSpec#AT_MOST} or * {@link android.view.View.MeasureSpec#EXACTLY} */ public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } /** * Extracts the size from the supplied measure specification. * * @param measureSpec the measure specification to extract the size from * @return the size in pixels defined in the supplied measure specification */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }
简单来说就是measure穿进去的值是一个int,这个32位的数字0-29位是他的size(大小)高2位是mode(模式)
UNSPECIFIED -0
EXACTLY-1
AT_MOST-2
这三个mode在View的计算中常用,但是有点走远了,有兴趣去看看源码。所以listview.measure(0,0)就是传进去了UNSPECIFIED size为0。之后就要计算高度
if (heightMode == MeasureSpec.UNSPECIFIED) { heightSize = mListPadding.top + mListPadding.bottom + childHeight + getVerticalFadingEdgeLength() * 2; }
measure之后才能调用listItem.getMeasuredHeight(),之前调用都会是0,现在就有了整个listview所有listItem的高度,最后加上item中间的缝隙就是最后的高度
/** * @return Returns the height of the divider that will be drawn between each item in the list. */ public int getDividerHeight() { return mDividerHeight; }
至于第二种方法我弄不清楚是为什么。看样子应该是中文对listItem的宽度有一些影响。
接下来看最靠谱的解决方法,主要就是重写了onMeasure方法
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);
根据刚才的分析,这里是设置一个高度,模式是AT_MOST,size是最大的Integer左移2位的数字。
之后计算这个ListView的高度
if (heightMode == MeasureSpec.AT_MOST) { // TODO: after first layout we should maybe start at the first visible position, not 0 heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1); }
可以发现不同的MODE的计算方法是不一样的。继续追
final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition, int maxHeight, int disallowPartialChildPosition) { final ListAdapter adapter = mAdapter; if (adapter == null) { return mListPadding.top + mListPadding.bottom; } // Include the padding of the list int returnedHeight = mListPadding.top + mListPadding.bottom; final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0; // The previous height value that was less than maxHeight and contained // no partial children int prevHeightWithoutPartialChild = 0; int i; View child; // mItemCount - 1 since endPosition parameter is inclusive endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition; final AbsListView.RecycleBin recycleBin = mRecycler; final boolean recyle = recycleOnMeasure(); final boolean[] isScrap = mIsScrap; for (i = startPosition; i <= endPosition; ++i) { child = obtainView(i, isScrap); measureScrapChild(child, i, widthMeasureSpec, maxHeight); if (i > 0) { // Count the divider for all but one child returnedHeight += dividerHeight; } // Recycle the view before we possibly return from the method if (recyle && recycleBin.shouldRecycleViewType( ((LayoutParams) child.getLayoutParams()).viewType)) { recycleBin.addScrapView(child, -1); } returnedHeight += child.getMeasuredHeight(); if (returnedHeight >= maxHeight) { // We went over, figure out which height to return. If returnedHeight > maxHeight, // then the i'th position did not fit completely. return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1) && (i > disallowPartialChildPosition) // We've past the min pos && (prevHeightWithoutPartialChild > 0) // We have a prev height && (returnedHeight != maxHeight) // i'th child did not fit completely ? prevHeightWithoutPartialChild : maxHeight; } if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) { prevHeightWithoutPartialChild = returnedHeight; } } // At this point, we went through the range of children, and they each // completely fit, so return the returnedHeight return returnedHeight; }
上面的方法就将会计算所有的ListView长度。最后只要super.onMeasure(widthMeasureSpec, expandSpec);就可以计算出ListVIew 的高度了。
第一个问题就解决了,下一篇博客继续解决问题,最后将会给出demo代码,需要的孩子可以好好研究。
相关文章推荐
- 完美实现Android ListView中的TextView的跑马灯效果
- android上改变listView的选中颜色
- 端口详解(3)-源端口
- 华为交换机的后缀详解
- DLL(Dynamic Linkable Library) 详解说明
- Delphi7中Listview的常用功能汇总
- Delphi控件ListView的属性及使用方法详解
- rudy 重载方法 详解
- android中ListView数据刷新时的同步方法
- Android提高之ListView实现自适应表格的方法
- Android中实现水平滑动(横向滑动)ListView示例
- ScrollView滚动条颜色的设置方法
- 留言板翻页的实现详解
- C#实现ListView选中项向上或向下移动的方法
- 前端轻量级MVC框架CanJS详解
- JS event使用方法详解
- 无边框窗口代码详解
- C# listview添加combobox到单元格的实现代码
- 刷新Activity中的scrollview示例(局部ui刷新)
- JAVASCRIPT THIS详解 面向对象