仿照京东搜索历史中,如果textview一行显示不全则进行换行。
先上图!!!
如图所示,自定义viewgroup实现京东搜索历史效果。
自定义ViewGroup详解首先我们来讲一下实现原理,自定义viewgroup实现的步骤:
重写onMesure() 方法计算子view的高度和 重写onLayout() 方法计算子view的摆放位置 onMesure方法详解onMesure方法是计算当前控件摆放子view后的总高度,我们的例子中计算高度的逻辑为,遍历子view,然后子view的宽度一直累加,如果子view的累加宽度大于viewgroup的总宽度,那么就应该把上一个子view换行显示。
首先通过measureChildren(widthMeasureSpec, heightMeasureSpec)方法来触发子view的onMesure方法来计算宽度高度。
然后通过MeasureSpec类获取viewgroup的显示模式跟宽高。
讲到显示模式,有必要讲解一下,显示模式分为三类:
MeasureSpec.EXACTLY:这种就相当于xml中设置了match_parent或者固定dp值。
MeasureSpec.AT_MOST:这种就相当于xml中设置了wrap_content。
MeasureSpec.UNSPECIFIED:这种就相当于没有设置宽高。
那么通过显示模式我们可以知道,我们京东的例子,宽度肯定不可能设置wrap_content,所以我们只处MeasureSpec.EXACTLY这种情况就可以了,高度的话,我们给定的一般是wrap_content,所以高度只处理MeasureSpec.AT_MOST就可以了,其它情况直接我们给定setMeasuredDimension(0, 0)即可。
直接上onMesure()的代码,都有注释,大家可以参考一下获取高度的方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
count++;
Log.e("lwd", "onmesure:" + count);
//将所有子view重新调用onMesure方法测量
measureChildren(widthMeasureSpec, heightMeasureSpec);
//获取测量模式跟测量高度
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
if (childCount == 0){
//如果没有子view,则容易没有存在的意义
setMeasuredDimension(0, 0);
}else{
viewMap = new HashMap();
//这里先处理宽度为match_parent跟固定值的情况,因为我们的容器肯定不为wrap_content
if (wMode == MeasureSpec.EXACTLY){
//计算容器高度,根据子view的宽度相加是否大于容器的宽度,进行计算总高度
int totalHeight = 0;
int curWidth = 0;
switch (hMode){
case MeasureSpec.EXACTLY:
break;
case MeasureSpec.AT_MOST://wrap_content时进行计算
int row = 0;
for (int i = 0; i = wSize){
//如果子view宽度大于等于容器的宽度,则高度累计,为一行
totalHeight += childH;
//当前width置为0
curWidth = 0;
addChild(viewMap, row, i);
//行数增加
row++;
totalHeight += MARGIN_VETICAL;
}else{
//如果子view小于容器宽度时,则计算子view的宽度和是否大于等于容器宽度,如果大于等于的话则进行换行
curWidth += childW;
if (i != 0){
//第一次计算不加margin
curWidth += MARGIN_HORIZTAL;//加上横向间距
}
if (curWidth > wSize){
totalHeight += childH;
curWidth = childW;
row++;
totalHeight += MARGIN_VETICAL;
}
addChild(viewMap, row, i);
if (totalHeight == 0){
totalHeight += MARGIN_VETICAL;
totalHeight += childH;
}
}
}
setMeasuredDimension(wSize, totalHeight);
break;
case MeasureSpec.UNSPECIFIED:
//如果没有设置高度的话,则容易没有存在的意义
setMeasuredDimension(0, 0);
break;
}
}else {
//如果没有宽度的话,则容易没有存在的意义
setMeasuredDimension(0, 0);
}
}
}
onLayout方法详解
我们知道onlayout方法是计算子view在viewgroup中的摆放位置的,那么我们获取到各个子view的上下左右坐标点即可,我们默认的初始坐标为父元素viewgroup的left,top,right,bottom,那么子元素只要宽高值知道,加上我们viewgroup的初始坐标即可,大家掌握view.layout(left,top,right,bottom);这个方法即可,分别是设置子veiw的坐标点位置的。
我们上代码:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!viewMap.isEmpty()){
Iterator iterator = viewMap.keySet().iterator();
int curX = l;
int curY = t;
int lastRowH = 0;
while (iterator.hasNext()){
int row = iterator.next();
curY += MARGIN_VETICAL;
ArrayList indexs = viewMap.get(row);
if (indexs != null && indexs.size() != 0){
for (int i = 0; i < indexs.size(); i++){
View child = getChildAt(indexs.get(i));
lastRowH = child.getMeasuredHeight();
int childWidth = child.getMeasuredWidth();
if (i != 0){
curX += MARGIN_HORIZTAL;
}
child.layout(curX, curY, curX + childWidth, curY + lastRowH);
curX += childWidth;
}
curY += lastRowH;
curX = l;
lastRowH = 0;
}
}
}
}
通过上面的讲解,我们大体明白了自定义ViewGroup的要领俩步走,onMesure跟onLayout方法重写,计算控件的宽高跟子view的位置。
福利来了!!!!!!!!全部代码如下!
public class SearchLayout extends ViewGroup {
private int count;
//设置水平垂直间距
private int MARGIN_HORIZTAL;
private int MARGIN_VETICAL;
private HashMap<Integer, ArrayList> viewMap;
public SearchLayout(Context context) {
super(context);
init(context);
}
public SearchLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
MARGIN_HORIZTAL = DisplayMetricsUtils.setDp(context, 30);
MARGIN_VETICAL = DisplayMetricsUtils.setDp(context, 30);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
count++;
Log.e("lwd", "onmesure:" + count);
//将所有子view重新调用onMesure方法测量
measureChildren(widthMeasureSpec, heightMeasureSpec);
//获取测量模式跟测量高度
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
if (childCount == 0){
//如果没有子view,则容易没有存在的意义
setMeasuredDimension(0, 0);
}else{
viewMap = new HashMap();
//这里先处理宽度为match_parent跟固定值的情况,因为我们的容器肯定不为wrap_content
if (wMode == MeasureSpec.EXACTLY){
//计算容器高度,根据子view的宽度相加是否大于容器的宽度,进行计算总高度
int totalHeight = 0;
int curWidth = 0;
switch (hMode){
case MeasureSpec.EXACTLY:
break;
case MeasureSpec.AT_MOST://wrap_content时进行计算
int row = 0;
for (int i = 0; i = wSize){
//如果子view宽度大于等于容器的宽度,则高度累计,为一行
totalHeight += childH;
//当前width置为0
curWidth = 0;
addChild(viewMap, row, i);
//行数增加
row++;
totalHeight += MARGIN_VETICAL;
}else{
//如果子view小于容器宽度时,则计算子view的宽度和是否大于等于容器宽度,如果大于等于的话则进行换行
curWidth += childW;
if (i != 0){
//第一次计算不加margin
curWidth += MARGIN_HORIZTAL;//加上横向间距
}
if (curWidth > wSize){
totalHeight += childH;
curWidth = childW;
row++;
totalHeight += MARGIN_VETICAL;
}
addChild(viewMap, row, i);
if (totalHeight == 0){
totalHeight += MARGIN_VETICAL;
totalHeight += childH;
}
}
}
setMeasuredDimension(wSize, totalHeight);
break;
case MeasureSpec.UNSPECIFIED:
//如果没有设置高度的话,则容易没有存在的意义
setMeasuredDimension(0, 0);
break;
}
}else {
//如果没有宽度的话,则容易没有存在的意义
setMeasuredDimension(0, 0);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!viewMap.isEmpty()){
Iterator iterator = viewMap.keySet().iterator();
int curX = l;
int curY = t;
int lastRowH = 0;
while (iterator.hasNext()){
int row = iterator.next();
curY += MARGIN_VETICAL;
ArrayList indexs = viewMap.get(row);
if (indexs != null && indexs.size() != 0){
for (int i = 0; i < indexs.size(); i++){
View child = getChildAt(indexs.get(i));
lastRowH = child.getMeasuredHeight();
int childWidth = child.getMeasuredWidth();
if (i != 0){
curX += MARGIN_HORIZTAL;
}
child.layout(curX, curY, curX + childWidth, curY + lastRowH);
curX += childWidth;
}
curY += lastRowH;
curX = l;
lastRowH = 0;
}
}
}
}
public void addChild(HashMap<Integer, ArrayList> map, int row, int index){
ArrayList columns = map.get(row);
if (columns != null && columns.size() != 0){
columns.add(index);
map.put(row, columns);
}else{
ArrayList integers = new ArrayList();
integers.add(index);
map.put(row, integers);
}
}
}
如果帮到大家,望点赞关注支持博主,谢谢!
作者:no_loafer