Android自定义ViewGroup,实现自动换行
学习《Android开发艺术探索》中自定义ViewGroup章节
自定义ViewGroup总结的知识点
一.自定义ViewGroup中,onMeasure理解
onMeasure(int widthMeasureSpec,int heightMeasureSpec); 需要进行补充的逻辑
1.对布局设置为wrap_content的兼容,具体查看下一篇日志的构建MeasureSpec的方法
最终实现是在onMeasure(...)方法中对LayoutParams设置为wrap_content的实现,在构建MeasureSpec时将,这个转换为MeasureSpec.AT_MOST这样的设置模式
注:下面模式一般适用于单View(不包括ViewGroup),因为ViewGroup设置为wrap_content时,是测量所有子View高/宽的和
单View
if(widthMode == MeasureSpec.AT_MOST && height == MeasureSpec.AT_MOST){
setMeasureDimission(测量的宽,测量的高);
}else if(widthMode == MeasureSpec.AT_MOST ){
setMeasureDimission(测量的宽,heightMeasureSpec);//heightMeasureSpec是父布局指定
}else if(heightMode == MeasureSpec.AT_MOST ){
setMeasureDimission(widthMeasureSpec,测量的高);//widthMeasureSpec是父布局指定
}
ViewGroup(此方法在ViewGroup中已经实现,在自定义ViewGroup中可直接调用)
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
* optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
* size is smaller than the size the view wants to be.
*
* @param size How big the view wants to be
* @param measureSpec Constraints imposed by the parent
* @return Size information bit mask as defined by
* {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
*/
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState&MEASURED_STATE_MASK);
}
2.onMeasure方法中参数的理解,widthMeasureSpec和heightMeasureSpec,在布局中去掉了margin参数后的值,将测量值通过setMeasureDimission设置该布局的宽和高
理解如下图
二,自定义ViewGroup中,onLayout的理解
1.onLayout(boolean changed, int l, int t, int r, int b) 对方法中参数,changed为当前布局是否改变
l,t,r,b是当前的布局的参数坐标,即有 当前控件宽度 width = r - l 高度 height = b - t;这里包括了padding的值
注意:在自定义ViewGroup的时候,实际计算得到的宽高均需要加入padding的值和子布局的margin值,而onMeasure或onLayout方法中传递过来的值,均不包含padding的值,这里要减去
总结:ViewGroup本身计算不用加入ViewGroup本身的margin,但要考虑padding变化,同时要考虑子View中margin的值
以上方法均需要调用子布局的measure和layout方法
例子: 自定义有自动换行功能的ViewGroup
package com.tongcheng.android.travel.widget; import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup; import com.tongcheng.android.R; /**
* Created by lcl11718 on 2016/12/6.
* 横向实现自动换行的ViewGroup
*/ public class HorizontalWrapLineLayout extends ViewGroup { private int mVerticalSpace;
private int mHorizontalSpace; public HorizontalWrapLineLayout(Context context) {
this(context, null);
} public HorizontalWrapLineLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
} private void init(Context context, AttributeSet attrs) {
setAttributeSet(context, attrs);
} /**
* 设置自定义属性
*
* @param context
* @param attrs
*/
private void setAttributeSet(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorizontalWrapLineLayout);
//属性中定义左右边距 padding margin
//属性中定义每一个距离垂直方向vertalSpace 和水平方向horizontalSpace
mVerticalSpace = (int) a.getDimension(R.styleable.HorizontalWrapLineLayout_verticalWrapSpace, 0);
mHorizontalSpace = (int) a.getDimension(R.styleable.HorizontalWrapLineLayout_horizontalWrapSpace, 0);
a.recycle();
} @Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
} @Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
} @Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
} @Override
protected boolean checkLayoutParams(LayoutParams p) {
return p instanceof MarginLayoutParams;
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 这里的高度和宽度是去掉margin的值
int horizontalPadding = getPaddingLeft() + getPaddingRight();
int measureWidth = horizontalPadding;
int verticalPadding = getPaddingTop() + getPaddingBottom();
int measureHeight = verticalPadding;
final int childCount = getChildCount(); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0) {
setMeasuredDimension(0, 0);
return;
}
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
//测量子View
if (childView.getVisibility() != View.GONE) {
measureChildWithMargins(childView, widthMeasureSpec, 0, heightMeasureSpec, 0);
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
int childMeasuredHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
int childMeasuredWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
measureWidth += childMeasuredWidth + mHorizontalSpace;
if (measureWidth > widthSpaceSize) {
measureHeight += childMeasuredHeight + mVerticalSpace;
measureWidth = getPaddingLeft() + getPaddingRight();
}
if (childCount - 1 == i && measureWidth > 0) {
measureHeight += childMeasuredHeight + mVerticalSpace;
}
}
}
if (heightSpaceMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthMeasureSpec, measureHeight);
} else {
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//这里的四角参数,是去掉Margin的参数
int childCount = getChildCount();
int left = getPaddingLeft();
int top = getPaddingTop();
int right = r - getPaddingRight(); int currentLeft = left;
int currentTop = top;
int lineHeight = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
//确定4个点
if (currentLeft + childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin >= right) {//换行
currentLeft = left;
currentTop += lineHeight + mVerticalSpace;
lineHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
} else {
lineHeight = Math.max(lineHeight, childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
childView.layout(
currentLeft + lp.leftMargin,
currentTop + lp.topMargin,
currentLeft + lp.leftMargin + childView.getMeasuredWidth(),
currentTop + lp.topMargin + childView.getMeasuredHeight());
currentLeft += childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + mHorizontalSpace;
}
}
}
上个版本是简略的实现,下面是优化之后,这个版本支持Grivity布局
package com.tongcheng.android.travel.widget; import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.LayoutDirection;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup; import com.tongcheng.android.R; import java.util.ArrayList;
import java.util.List; /**
* Created by lcl11718 on 2016/12/8.
* 自动换行容器
*/ public class AutoRowLayout extends ViewGroup { /**
* 平均分配
*/
public static final int AVERAGE = 0;
/**
* 自适应
*/
public static final int ADAPTIVE = 1;
/**
* style
*/
private int mStyleType;
/**
* 列之间间距
*/
private int mColumnSpace;
/**
* 行之间间距
*/
private int mRowSpace;
/**
* 列数量
*/
private int mColumnNum;
/**
* 行数
*/
private int mRowNum; /**
* 最大行数
*/
private int mMaxLine;
/**
* 对齐方式
*/
private int mGravity = Gravity.START | Gravity.TOP; /**
* 自适应测量算法 记录行数
*/
private List<Integer> mAdaptiveLines = new ArrayList<Integer>(); public AutoRowLayout(Context context) {
this(context, null, 0);
} public AutoRowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public AutoRowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setAttributes(context, attrs);
} /**
* set basic attrs
*
* @param context
* @param attrs
*/
public void setAttributes(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.AutoRowLayout);
mStyleType = ta.getInt(R.styleable.AutoRowLayout_style_type, 0);//0 是平均分
mColumnSpace = (int) ta.getDimension(R.styleable.AutoRowLayout_columnSpace, 0);
mRowSpace = (int) ta.getDimension(R.styleable.AutoRowLayout_rowSpace, 0);
mColumnNum = ta.getInt(R.styleable.AutoRowLayout_columnNum, 0);
mRowNum = ta.getInt(R.styleable.AutoRowLayout_rowNum, 0);
mMaxLine = ta.getInt(R.styleable.AutoRowLayout_maxLine, 0);
mGravity = ta.getInt(R.styleable.AutoRowLayout_android_gravity, 0);
ta.recycle();
} /**
* set style type
*
* @param type
*/
public void setStyleType(int type) {
this.mStyleType = type;
} public void setColumnSpace(int columnSpace) {
this.mColumnSpace = columnSpace;
} public void setRowSpace(int rowSpace) {
this.mRowSpace = rowSpace;
} public void setColumnNum(int columnNum) {
this.mColumnNum = columnNum;
} public void setRowNum(int rowNum) {
this.mRowNum = rowNum;
} public void setGravity(int gravity) {
this.mGravity = gravity;
} /***********************************
* 加入Margin start
**************************************/
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
} @Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
} @Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
} @Override
protected boolean checkLayoutParams(LayoutParams p) {
return p != null && p instanceof MarginLayoutParams;
} /***********************************
* 加入Margin end
**************************************/ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mStyleType == AVERAGE) {
measureAverage(widthMeasureSpec, heightMeasureSpec);
} else if (mStyleType == ADAPTIVE) {
measureAdaptive(widthMeasureSpec, heightMeasureSpec);
}
} /**
* 平均测量算法
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
private void measureAverage(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
if (count == 0) {
setMeasuredDimension(0, 0);
return;
} int widthPadding = getPaddingLeft() + getPaddingRight();
int heightPadding = getPaddingTop() + getPaddingBottom();
int childState = 0;
// get a max child width for widthMeasureSpec
int maxChildWidth = getMaxChildWidth(count, widthMeasureSpec, heightMeasureSpec, childState);
int maxWidth = 0; if (mColumnNum > 0) {
maxWidth = maxChildWidth * mColumnNum + (mColumnNum - 1) * mColumnSpace + widthPadding;
} else {
throw new RuntimeException("autoRowLayout must set a column num");
} int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec); View firstChild = getChildAt(0);
int totalRowNumHeight = mRowNum * firstChild.getMeasuredHeight() + (mRowNum - 1) * mRowSpace + heightPadding;
int maxHeight = mRowNum > 0 ? totalRowNumHeight : getTotalHeightNoRows(count, heightPadding, firstChild); int limitMaxWidth = (widthMeasureSize - widthPadding - (mColumnNum - 1) * mColumnSpace) / mColumnNum;
int maxAllowWidth = MeasureSpec.EXACTLY == widthMeasureMode ? limitMaxWidth : Math.min(maxChildWidth, limitMaxWidth);
setWidthLayoutParams(count, maxAllowWidth); maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
int heightAndState = resolveSizeAndState(maxHeight, heightMeasureSpec, 0);
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightAndState);
} /**
* set per max width of views
*
* @param count
* @param maxChildWidth
*/
private void setWidthLayoutParams(int count, int maxChildWidth) {
for (int index = 0; index < count; index++) {
View child = getChildAt(index);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = new MarginLayoutParams(maxChildWidth, child.getLayoutParams().height);
child.setLayoutParams(lp);
}
} /**
* get max height not set row num
*
* @param count
* @param heightPadding
* @param child
* @return
*/
private int getTotalHeightNoRows(int count, int heightPadding, View child) { int allowRowNums = count / mColumnNum;
int maxHeight = allowRowNums * child.getMeasuredHeight() + (allowRowNums - 1) * mRowSpace + heightPadding; if (count % mColumnNum > 0) {
maxHeight += child.getMeasuredHeight() + mRowSpace;
}
return maxHeight;
} /**
* get a max child width for this layout
*
* @param count
* @param widthMeasureSpec
* @param heightMeasureSpec
* @return
*/
private int getMaxChildWidth(int count, int widthMeasureSpec, int heightMeasureSpec, int childState) {
int maxChildWidth = 0;
for (int index = 0; index < count; index++) {
View child = getChildAt(index);
if (child.getVisibility() == View.GONE) {
continue;
}
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
childState = combineMeasuredStates(childState, child.getMeasuredState());
maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
}
return maxChildWidth;
} /**
* 自适应测量算法
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
private void measureAdaptive(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
if (count == 0) {
setMeasuredDimension(0, 0);
return;
}
int widthPadding = getPaddingLeft() + getPaddingRight();
int heightPadding = getPaddingTop() + getPaddingBottom();
int width = widthPadding;
int height = heightPadding;
int lineMaxHeight = 0; int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int childState = 0;
mAdaptiveLines.clear();
for (int index = 0; index < count; index++) {
View child = getChildAt(index);
if (child.getVisibility() == View.GONE) {
continue;
}
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
if (width + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin > widthSize) {//换行
height += lineMaxHeight + mRowSpace;
width = widthPadding + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin + mColumnSpace;
lineMaxHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
mAdaptiveLines.add(index);
} else {
lineMaxHeight = Math.max(lineMaxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
width += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + mColumnSpace;
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
}
mAdaptiveLines.add(count);
height += lineMaxHeight; height = Math.max(height, getSuggestedMinimumHeight());
int heightAndState = resolveSizeAndState(height, heightMeasureSpec, 0);
setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState), heightAndState);
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mStyleType == AVERAGE) {
layoutAverage(l, t, r, b);
} else if (mStyleType == ADAPTIVE) {
layoutAdaptive(l, t, r, b);
}
} /**
* 平均布局算法
*
* @param left
* @param top
* @param right
* @param bottom
*/
private void layoutAverage(int left, int top, int right, int bottom) {
int count = getChildCount();
if (count == 0) {
return;
}
int childLeft;
int childTop = 0;
int lineMaxHeight = 0; int totalRowNum = count / mColumnNum + (count % mColumnNum == 0 ? 0 : 1); int maxRowNum = mRowNum > 0 ? mRowNum : totalRowNum; for (int rowNum = 0; rowNum < maxRowNum; rowNum++) {
childLeft = getPaddingLeft();
childTop += rowNum > 0 ? lineMaxHeight + mRowSpace : getPaddingTop();
for (int columnNum = 0; columnNum < mColumnNum; columnNum++) {
if (columnNum + mColumnNum * rowNum >= count) {
break;
}
View child = getChildAt(columnNum + mColumnNum * rowNum);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); child.layout(
childLeft + lp.leftMargin,
childTop + lp.topMargin,
childLeft + lp.leftMargin + lp.width,
childTop + lp.topMargin + lp.height);
childLeft += lp.width + lp.leftMargin + lp.rightMargin + mColumnSpace;
lineMaxHeight = Math.max(lineMaxHeight, lp.height + lp.topMargin + lp.bottomMargin);
}
}
} /**
* 自适应布局算法
*
* @param left
* @param top
* @param right
* @param bottom
*/
private void layoutAdaptive(int left, int top, int right, int bottom) {
int count = getChildCount();
if (count == 0) {
return;
}
int width = right - left;
int childSpace = width - getPaddingLeft() - getPaddingRight(); int limitLines = mMaxLine > 0 ? Math.min(mMaxLine, mAdaptiveLines.size()) : mAdaptiveLines.size();
int[] childLefts = new int[limitLines]; int totalChildHeight = 0; for (int rowIndex = 0; rowIndex < limitLines; rowIndex++) {
int startRowIndex = rowIndex > 0 ? mAdaptiveLines.get(rowIndex - 1) : 0;
int endRowIndex = mAdaptiveLines.get(rowIndex);
int maxChildWidth = 0;
int lineMaxHeight = 0;
for (; startRowIndex < endRowIndex; startRowIndex++) {
View child = getChildAt(startRowIndex);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
maxChildWidth += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
if (startRowIndex != endRowIndex - 1) {
maxChildWidth += mColumnSpace;
}
lineMaxHeight = Math.max(lineMaxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
}
totalChildHeight += lineMaxHeight;
childLefts[rowIndex] = getChildLeft(childSpace - maxChildWidth);
} int childTop = getChildTop(top, bottom, totalChildHeight);
for (int rowIndex = 0; rowIndex < limitLines; rowIndex++) {
int startRowIndex = rowIndex > 0 ? mAdaptiveLines.get(rowIndex - 1) : 0;
int endRowIndex = mAdaptiveLines.get(rowIndex);
int childLeft = childLefts[rowIndex];
for (; startRowIndex < endRowIndex; startRowIndex++) {
View child = getChildAt(startRowIndex);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
child.layout(
childLeft + lp.leftMargin,
childTop + lp.topMargin,
childLeft + lp.leftMargin + child.getMeasuredWidth(),
childTop + lp.topMargin + child.getMeasuredHeight());
childLeft += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + mColumnSpace;
} }
} /**
* get top of child View
*
* @param top
* @param bottom
* @param totalChildHeight
* @return
*/
private int getChildTop(int top, int bottom, int totalChildHeight) {
int childTop;
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = getPaddingTop() + bottom - top - totalChildHeight;
break; // mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = getPaddingTop() + (bottom - top - totalChildHeight) / 2;
break; case Gravity.TOP:
default:
childTop = getPaddingTop();
break;
}
return childTop;
} /**
* get left value of row
*
* @param widthSpace
* @return
*/
private int getChildLeft(int widthSpace) {
int childLeft;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
final int absoluteGravity = Gravity.getAbsoluteGravity(minorGravity, LayoutDirection.LTR);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = getPaddingLeft() + (widthSpace / 2);
break; case Gravity.RIGHT:
childLeft = getPaddingLeft() + widthSpace;
break; case Gravity.LEFT:
default:
childLeft = getPaddingLeft();
break;
}
return childLeft;
}
}
Android自定义ViewGroup,实现自动换行的更多相关文章
- Android自定义组件之自动换行及宽度自适应View:WordWrapView
目的: 自定义一个ViewGroup,里面的子view都是TextView,每个子view TextView的宽度随内容自适应且每行的子View的个数自适应,并可以自动换行 一:效果图 二:代码 整 ...
- android自定义viewgroup之我也玩瀑布流
先看效果图吧, 继上一篇<android自定义viewgroup实现等分格子布局>中实现的布局效果,这里稍微有些区别,每个格子的高度不规则,就是传说的瀑布流布局,一般实现这种效果,要么用第 ...
- Android自定义ViewGroup(四、打造自己的布局容器)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51500304 本文出自:[openXu的博客] 目录: 简单实现水平排列效果 自定义Layo ...
- Android自定义ViewGroup
视图分类就两类,View和ViewGroup.ViewGroup是View的子类,ViewGroup可以包含所有的View(包括ViewGroup),View只能自我描绘,不能包含其他View. 然而 ...
- android自定义viewgroup实现等分格子布局
先上效果图: 实现这样的效果: 一般的思路就是,直接写布局文件,用LinearLayout 嵌套多层子LinearLayout,然后根据权重layout_weight可以达到上面的效果 还有就是利用g ...
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu
示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这里我简单说明一下用自定义ViewGroup来实现. 实现方法:我们自定义一个ViewGroup实现左右滑动, ...
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]
http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...
- android自定义viewgroup初步之一----抽屉菜单
转载请注明出处 http://blog.csdn.net/wingichoy/article/details/47832151 几天前在慕课网上看到鸿洋老师的 自定义卫星菜单,感觉很有意思,于是看完视 ...
- Android 自定义ViewGroup手把手教你实现ArcMenu
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这 ...
随机推荐
- N个数依次入栈,出栈顺序有多少种?
对于每一个数来说,必须进栈一次.出栈一次.我们把进栈设为状态‘1’,出栈设为状态‘0’.n个数的所有状态对应n个1和n个0组成的2n位二进制数.由于等待入栈的操作数按照1‥n的顺序排列.入栈的操作数b ...
- MySQL半同步复制
从MySQL5.5开始,MySQL以插件的形式支持半同步复制.如何理解半同步呢?首先我们来看看异步,全同步的概念 异步复制(Asynchronous replication) MySQL默认的复制即是 ...
- IDDD 实现领域驱动设计-架构之经典分层
上一篇:<IDDD 实现领域驱动设计-上下文映射图及其相关概念> 在<实现领域驱动设计>书中,分层的概念作者讲述的很少,也就几页的内容,但对于我来说,有很多的感触需要诉说.之前 ...
- MVC, MVP, MVVM比较以及区别(上)
MVC, MVP和MVVM都是用来解决界面呈现和逻辑代码分离而出现的模式.以前只是对它们有部分的了解,没有深入的研究过,对于一些里面的概念和区别也是一知半解.现在一边查资料,并结合自己的理解,来谈一下 ...
- Java ConcurrentHashMap Example and Iterator--转
原文地址:http://www.journaldev.com/122/java-concurrenthashmap-example-iterator#comment-27448 Today we wi ...
- 不要给<a>设置outline:none
outline属性有什么作用 原文链接 a{outline:none} do not do it 当用户使用tab键进行链接切换时,该属性会在当前选中的链接(获得焦点)使用该属性,一般来说是虚线框 的 ...
- WebGIS中基于AGS的画圆查询简析以及通过Polygon来构造圆的算法
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.背景 某个项目需求中需要在前端进行画圆查询,将圆范围上的多边形要素 ...
- C#枚举类型的常用操作总结
枚举类型是定义了一组"符号名称/值"配对.枚举类型是强类型的.每个枚举类型都是从system.Enum派生,又从system.ValueType派生,而system.ValueTy ...
- 第0/24周 SQL Server 性能调优培训引言
大家好,这是我在博客园写的第一篇博文,之所以要开这个博客,是我对MS SQL技术学习的一个兴趣记录. 作为计算机专业毕业的人,自己对技术的掌握总是觉得很肤浅,博而不专,到现在我才发现自己的兴趣所在,于 ...
- Nancy之区域和分部视图的使用
一.前言 在MVC中,区域(Area)和分部视图(PartialView)应该是我们用的十分频繁的两个东西 今天我们就在Nancy中,把这两个东西简单的用起来! 二.简单使用之区域 区域,无论是对 ...