Android 高逼格纯代码实现类似微信钱包带分割线的GridView
前言
通过上两篇关于自定view的文章,在自定义view基础上,今天给大家带来怎么代码自定义九宫格子,并且显示支付宝一样的九宫格效果:
导读:
目标效果:
自定义GridView
继承ViewGroup ,我们复写测量,绘制,布局方法,并且将此类定义为抽象类,用于绘制基础的宫格视图。
onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取给定尺寸
int width = MeasureSpec.getSize(widthMeasureSpec);
int nChildCount = getIconViewCount();
// 计算总行数和总列数
mColCount = getColCount();
int row = nChildCount / mColCount;
int col = nChildCount % mColCount;
mRowCount = ((col == 0) ? row : row + 1);
// 计算单元格尺寸
int dividerW = getDividerWidth() * (mColCount + 1);
mCellWidth = 1.0f * (width - getPaddingLeft() - getPaddingRight() - dividerW) / mColCount;
// 遍历子view的尺寸设置
for (int i = 0; i < nChildCount; i++) {
View child = getIconView(i);
child.measure((int) mCellWidth, (int) mCellHeight);
}
// slot
int slotHeightMeasureSpec =
getChildMeasureSpec(heightMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
mSlotView.measure(widthMeasureSpec, slotHeightMeasureSpec);
int slotRow = getSlotRow();
mSlotOffsetY = (int) (slotRow * mCellHeight + (slotRow + 1) * getDividerWidth());
// 计算总尺寸
int nViewHeight = Math.round(mRowCount * mCellHeight + (mRowCount + 1) * getDividerWidth());
nViewHeight = nViewHeight + mSlotView.getMeasuredHeight();
if (slotRow < mRowCount && mSlotView.getMeasuredHeight() > 0) {
nViewHeight = nViewHeight + getDividerWidth();
}
nViewHeight = nViewHeight + getPaddingTop() + getPaddingBottom();
// 设置总尺寸
setMeasuredDimension(width, nViewHeight);
// 分割线初始化
initDividerData();
}
onLayout
@Override
protected void onLayout(boolean change, int l, int t, int r, int b) {
// banner视图
mSlotView.layout(0, mSlotOffsetY,
mSlotView.getMeasuredWidth(), mSlotOffsetY + mSlotView.getMeasuredHeight());
// 单元视图
int count = getIconViewCount();
for (int i = 0; i < count; i++) {
View childView = getIconView(i);
BdAnimInfo animInfo = getViewHolder(childView).getAnimInfo();
layoutItem(childView, i, animInfo.isMove());
}
}
onDraw
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 分割线
if (mIsDividerEnable) {
drawDivider(canvas);
}
}
完成了基本的测量和layout,和整体item绘制后,接着继续画分割线:
/**
* 绘制分割线
*
* @param aCanvas 画布
* @param aPaint 画笔
* @param aLineOffset 偏移
* @param aLineWidth 线宽度
*/
public void drawCrossLine(Canvas aCanvas, Paint aPaint, int aLineOffset, int aLineWidth) {
// 变量
int max;
int offset;
int lastCol = getIconViewCount() % mColCount;
int bannerRow = getSlotRow();
Drawable divider = getDivider();
// Horizontal line
max = mDividerRow;
int left = getPaddingLeft();
int right = getMeasuredWidth() - getPaddingRight();
offset = getPaddingTop() + aLineOffset;
for (int i = 0; i < max; i++) {
if (i == max - 1) { // 最后一行停留在某个格子右边
// 右边终点重新确定
if (lastCol > 0) {
right = (int) (getPaddingLeft() + lastCol * mCellWidth + (lastCol + 1) * getDividerWidth());
}
} else if (i == bannerRow && mSlotView.getMeasuredHeight() > 0) { // banner特殊处理
divider.setBounds(left, offset, right, offset + aLineWidth);
divider.draw(aCanvas);
offset += getDividerWidth() + mSlotView.getMeasuredHeight();
}
// 绘制
divider.setBounds(left, offset, right, offset + aLineWidth);
divider.draw(aCanvas);
offset += mCellHeight + getDividerWidth();
}
// Vertical line
max = mDividerCol;
int top = getPaddingTop();
int bottom = 0;
for (int i = 0; i < max; i++) {
offset = (int) (getPaddingLeft() + aLineOffset + i * (mCellWidth + getDividerWidth()));
if (i > lastCol && lastCol > 0) {
bottom = (int) (getPaddingTop() + mCellHeight * (mRowCount - 1) + getDividerWidth() * mRowCount);
} else {
bottom = (int) (getPaddingTop() + mCellHeight * mRowCount + getDividerWidth() * (mRowCount + 1));
}
if (bottom >= mSlotOffsetY && mSlotView.getMeasuredHeight() > 0) {
int midBottom = mSlotOffsetY - getDividerWidth();
int midTop = mSlotOffsetY + mSlotView.getMeasuredHeight();
bottom += mSlotView.getMeasuredHeight();
divider.setBounds(offset, top, offset + aLineWidth, midBottom);
divider.draw(aCanvas);
divider.setBounds(offset, midTop, offset + aLineWidth, bottom);
divider.draw(aCanvas);
} else {
divider.setBounds(offset, top, offset + aLineWidth, bottom);
divider.draw(aCanvas);
}
}
}
以上便是最核心的三个方法了,其他代码可以下面的完整代码,
* 宫格视图抽象类 其子类为baseGridview
* 提供最基本的绘制和属性初始化工作,添加删除工作以及条目
* @author LIUYONGKUI
*/
public abstract class PaAbsGridView extends ViewGroup {
/**
* 视图holder的标志,用于标记View中的tag
*/
public static final int VIEW_HOLDER_TAG = 0x0fffff00;
/**
* 默认列数
*/
public static final int DEF_COLCOUNT = 3;
/**
* 行数
*/
private int mRowCount;
/**
* 列数
*/
private int mColCount;
/**
* 宫格宽度
*/
private float mCellWidth;
/**
* 宫格高度
*/
private int mCellHeight;
/**
* 临时区域
*/
private Rect mAssistRect = new Rect();
/**
* 插槽
*/
private FrameLayout mSlotView;
/**
* 插槽的偏移量: y
*/
private int mSlotOffsetY;
/**
* 插槽的偏移行
*/
private int mSlotOffsetRow;
/**
* 分割线是否显示
*/
private boolean mIsDividerEnable;
/**
* 分割线
*/
private int mDividerWidth;
/**
* 交叉线行数
*/
private int mDividerRow;
/**
* 交叉线列数
*/
private int mDividerCol;
/**
* 分割线图片(白天)
*/
private Drawable mDividerDay;
/**
* 分割线图片(夜晚)
*/
private Drawable mDividerNight;
/**
* 数据适配器
*/
private BaseAdapter mAdapter;
/**
* 构造函数
*
* @param context 上下文
* @param aAdapter adapter
* @param aColCount 列数
*/
public PaAbsGridView(Context context, BaseAdapter aAdapter, int aColCount) {
super(context);
// 基本属性
this.setWillNotDraw(false);
mColCount = aColCount;
mAdapter = aAdapter;
mAdapter.registerDataSetObserver(new BdCellDataObserver());
// 视图属性
mCellHeight = getCellHeight();
mDividerWidth = getDividerWidthDef();
mDividerDay = getDividerDay();
mDividerNight = getDividerDay();
// 刷新视图
refreshViews();
}
/**
* 初始化视图
*/
public void refreshViews() {
// 初始化banner插槽
if (mSlotView == null) {
mSlotView = new FrameLayout(getContext());
mSlotOffsetRow = 3;
}
final List<View> cacheList = new ArrayList<View>();
for (int i = 0; i < getIconViewCount(); i++) {
cacheList.add(getIconView(i));
}
removeAllIconViews();
// 添加单元项
for (int i = 0; i < mAdapter.getCount(); i++) {
addItemView(queryCacheItemView(cacheList, i), false);
}
cacheList.clear();
}
/**
* getDrviderDay
* @return
*/
protected Drawable getDividerDay() {
return getResources().getDrawable(R.drawable.home_divider_day);
}
/**
* getDrivderNight
* @return
*/
protected Drawable getDividerNight() {
return getResources().getDrawable(R.drawable.home_divider_night);
}
/**
* @return 适配器
*/
public BaseAdapter getAdapter() {
return mAdapter;
}
/**
* @param aIsEnable 分割线是否有效
*/
public void setIsDividerEnable(boolean aIsEnable) {
mIsDividerEnable = aIsEnable;
}
/**
* 获取缓存的单元视图
*
* @param aCacheList 缓存列表
* @param aDataIndex 数据索引
* @return 单元视图
*/
private View queryCacheItemView(List<View> aCacheList, int aDataIndex) {
final int count = ((aCacheList != null) ? aCacheList.size() : 0);
View cacheView = null;
// 寻找数据项匹配的单元视图
Object item = mAdapter.getItem(aDataIndex);
if (item != null) {
for (int i = 0; i < count; i++) {
View itemView = aCacheList.get(i);
Object itemData = getViewHolder(itemView).getData();
if ((itemData != null) && (itemData.equals(item))) {
aCacheList.remove(i);
cacheView = itemView;
break;
}
}
}
// 寻找类型匹配的单元视图
View targetView = mAdapter.getView(aDataIndex, cacheView, null);
getViewHolder(targetView).setData(item);
return targetView;
}
/**
* 往插槽里填充视图
*
* @param aView 视图
*/
public void addToSlot(View aView) {
if ((aView != null) && (mSlotView.indexOfChild(aView) < 0)) {
mSlotView.addView(aView,
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
}
}
/**
* 添加单元格视图
*
* @param aIconView 图标单元格视图
*/
public void addIconView(View aIconView) {
addView(aIconView);
}
/**
* 添加单元格视图
*
* @param aIconView 单元格视图
* @param aIconIndex 位置
*/
public void addIconView(View aIconView, int aIconIndex) {
addView(aIconView, aIconIndex + 1);
}
/**
* 移除单元格视图
*
* @param aIconView 单元格视图
*/
public void removeIconView(View aIconView) {
removeView(aIconView);
}
/**
* @return 单元格视图的总个数
*/
public int getIconViewCount() {
return getChildCount() - 1;
}
/**
* @param aIndex 索引
* @return 单元格视图
*/
public View getIconView(int aIndex) {
return getChildAt(aIndex + 1);
}
/**
* 移除所有的单元格视图
*/
public void removeAllIconViews() {
removeAllViews();
addView(mSlotView);
}
/**
* 释放所有视图资源
*/
public void releaseAllViews() {
removeAllViews();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取给定尺寸
int width = MeasureSpec.getSize(widthMeasureSpec);
int nChildCount = getIconViewCount();
// 计算总行数和总列数
mColCount = getColCount();
int row = nChildCount / mColCount;
int col = nChildCount % mColCount;
mRowCount = ((col == 0) ? row : row + 1);
// 计算单元格尺寸
int dividerW = getDividerWidth() * (mColCount + 1);
mCellWidth = 1.0f * (width - getPaddingLeft() - getPaddingRight() - dividerW) / mColCount;
// 遍历子view的尺寸设置
for (int i = 0; i < nChildCount; i++) {
View child = getIconView(i);
child.measure((int) mCellWidth, (int) mCellHeight);
}
// slot
int slotHeightMeasureSpec =
getChildMeasureSpec(heightMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
mSlotView.measure(widthMeasureSpec, slotHeightMeasureSpec);
int slotRow = getSlotRow();
mSlotOffsetY = (int) (slotRow * mCellHeight + (slotRow + 1) * getDividerWidth());
// 计算总尺寸
int nViewHeight = Math.round(mRowCount * mCellHeight + (mRowCount + 1) * getDividerWidth());
nViewHeight = nViewHeight + mSlotView.getMeasuredHeight();
if (slotRow < mRowCount && mSlotView.getMeasuredHeight() > 0) {
nViewHeight = nViewHeight + getDividerWidth();
}
nViewHeight = nViewHeight + getPaddingTop() + getPaddingBottom();
// 设置总尺寸
setMeasuredDimension(width, nViewHeight);
// 分割线初始化
initDividerData();
}
@Override
protected void onLayout(boolean change, int l, int t, int r, int b) {
// banner视图
mSlotView.layout(0, mSlotOffsetY,
mSlotView.getMeasuredWidth(), mSlotOffsetY + mSlotView.getMeasuredHeight());
// 单元视图
int count = getIconViewCount();
for (int i = 0; i < count; i++) {
View childView = getIconView(i);
BdAnimInfo animInfo = getViewHolder(childView).getAnimInfo();
layoutItem(childView, i, animInfo.isMove());
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 分割线
if (mIsDividerEnable) {
drawDivider(canvas);
}
}
/**
* 初始化分割线数据
*/
public void initDividerData() {
// 计算绘制行数
int row = getIconViewCount() / mColCount;
int col = getIconViewCount() % mColCount;
mDividerRow = ((col == 0) ? row + 1 : row + 2);
// 计算绘制列
mDividerCol = mColCount + 1;
}
/**
* 绘制分割线
*
* @param aCanvas 画布
*/
public void drawDivider(Canvas aCanvas) {
// 绘制交叉线
drawCrossLine(aCanvas, null, 0, getDividerWidth());
}
/**
* @return 分割线图片
*/
public Drawable getDivider() {
/* if (BdThemeManager.getInstance().isNightT5()) {
return mDividerNight;
} else {
return mDividerDay;
}*/
return mDividerDay;
}
/**
* 绘制分割线
*
* @param aCanvas 画布
* @param aPaint 画笔
* @param aLineOffset 偏移
* @param aLineWidth 线宽度
*/
public void drawCrossLine(Canvas aCanvas, Paint aPaint, int aLineOffset, int aLineWidth) {
// 变量
int max;
int offset;
int lastCol = getIconViewCount() % mColCount;
int bannerRow = getSlotRow();
Drawable divider = getDivider();
// Horizontal line
max = mDividerRow;
int left = getPaddingLeft();
int right = getMeasuredWidth() - getPaddingRight();
offset = getPaddingTop() + aLineOffset;
for (int i = 0; i < max; i++) {
if (i == max - 1) { // 最后一行停留在某个格子右边
// 右边终点重新确定
if (lastCol > 0) {
right = (int) (getPaddingLeft() + lastCol * mCellWidth + (lastCol + 1) * getDividerWidth());
}
} else if (i == bannerRow && mSlotView.getMeasuredHeight() > 0) { // banner特殊处理
divider.setBounds(left, offset, right, offset + aLineWidth);
divider.draw(aCanvas);
offset += getDividerWidth() + mSlotView.getMeasuredHeight();
}
// 绘制
divider.setBounds(left, offset, right, offset + aLineWidth);
divider.draw(aCanvas);
offset += mCellHeight + getDividerWidth();
}
// Vertical line
max = mDividerCol;
int top = getPaddingTop();
int bottom = 0;
for (int i = 0; i < max; i++) {
offset = (int) (getPaddingLeft() + aLineOffset + i * (mCellWidth + getDividerWidth()));
if (i > lastCol && lastCol > 0) {
bottom = (int) (getPaddingTop() + mCellHeight * (mRowCount - 1) + getDividerWidth() * mRowCount);
} else {
bottom = (int) (getPaddingTop() + mCellHeight * mRowCount + getDividerWidth() * (mRowCount + 1));
}
if (bottom >= mSlotOffsetY && mSlotView.getMeasuredHeight() > 0) {
int midBottom = mSlotOffsetY - getDividerWidth();
int midTop = mSlotOffsetY + mSlotView.getMeasuredHeight();
bottom += mSlotView.getMeasuredHeight();
divider.setBounds(offset, top, offset + aLineWidth, midBottom);
divider.draw(aCanvas);
divider.setBounds(offset, midTop, offset + aLineWidth, bottom);
divider.draw(aCanvas);
} else {
divider.setBounds(offset, top, offset + aLineWidth, bottom);
divider.draw(aCanvas);
}
}
}
/**
* 增加数据项对应的子项
*
* @param aItemView item view
* @param isAfterInit init
*/
private void addItemView(View aItemView, boolean isAfterInit) {
if (isAfterInit) {
addIconView(aItemView, getIconViewCount() - 1);
} else {
addIconView(aItemView);
}
}
/**
* 布局子项;可重载,允许子类有实现其它功能的机会
*
* @param aChild 子项
* @param aPos 位置
* @param aIsAnim 是否做动画
*/
public void layoutItem(View aChild, int aPos, boolean aIsAnim) {
// 获取视图的动画信息
BdViewHolder holder = getViewHolder(aChild);
BdAnimInfo animInfo = holder.getAnimInfo();
// 获取相应位置的矩形区域
final Rect r = mAssistRect;
getChildArea(r, aChild, aPos);
// 如果动画进行中,重新定义动画属性; 否则直接布局
if (aIsAnim) {
animInfo.beginMove(aChild.getLeft(), aChild.getTop(), r.left, r.top);
} else {
aChild.layout(r.left, r.top, r.right, r.bottom);
}
// 重新定义视图位置
holder.setPosition(aPos);
}
/**
* 更新动画中子项的状态
*/
public void updateAnimInfo(float aFactor) {
// 更新移动项
int count = getIconViewCount();
for (int i = 0; i < count; i++) {
View itemView = getIconView(i);
BdAnimInfo animInfo = getViewHolder(itemView).getAnimInfo();
if (animInfo.isMove()) {
animInfo.move(aFactor);
}
}
}
/**
* 更新动画中子项的布局
*/
public void updateAnimLayout(float aFactor) {
// 重新布局
int count = getIconViewCount();
for (int i = 0; i < count; i++) {
View itemView = getIconView(i);
BdAnimInfo animInfo = getViewHolder(itemView).getAnimInfo();
if (animInfo.isMove()) {
itemView.layout(animInfo.getX(), animInfo.getY(),
animInfo.getX() + itemView.getMeasuredWidth(),
animInfo.getY() + itemView.getMeasuredHeight());
// 结束标记
if (aFactor == 1.0f) {
animInfo.endMove();
}
}
}
}
/**
* 获取给定位置上的子项区域
*
* @param outRect 存储区域
* @param child 子项
* @param pos 子项位置
*/
public void getChildArea(Rect outRect, View child, int pos) {
getCellArea(outRect, pos);
int offsetX = (int) (outRect.left + (mCellWidth - child.getMeasuredWidth()) / 2);
int offsetY = (int) (outRect.top + (mCellHeight - child.getMeasuredHeight()) / 2);
outRect.set(offsetX, offsetY, offsetX + child.getMeasuredWidth(), offsetY + child.getMeasuredHeight());
}
/**
* 获取单元格的区域
*
* @param outRect 所在区域
* @param pos 单元格位置
*/
public void getCellArea(Rect outRect, int pos) {
int row = pos / mColCount;
int col = pos % mColCount;
int bannerRow = (mRowCount > mSlotOffsetRow ? mSlotOffsetRow : mRowCount);
int startX = (int) (getPaddingLeft() + (col + 1) * getDividerWidth() + col * mCellWidth);
int startY = (int) (getPaddingTop() + (row + 1) * getDividerWidth() + row * mCellHeight);
// 如果banner在宫格的上方存在,那么要多加一条分割线
if ((row >= bannerRow) && mSlotView.getMeasuredHeight() > 0) {
startY = startY + mSlotView.getMeasuredHeight() + getDividerWidth();
}
outRect.set(startX, startY, (int) (startX + mCellWidth), (int) (startY + mCellHeight));
}
/**
* @return banner所在行
*/
public int getSlotRow() {
return (mRowCount > mSlotOffsetRow ? mSlotOffsetRow : mRowCount);
}
/**
* 快速定位位置
*
* @param x 当前坐标x
* @param y 当前坐标y
* @return 命中的单元格索引
*/
public int getCellPosition(int x, int y) {
// 先去除banner的偏移
if (y > mSlotOffsetY) {
y -= mSlotView.getMeasuredHeight();
}
int row = (int) ((y - getPaddingTop()) / mCellHeight);
int col = (int) ((x - getPaddingLeft()) / mCellWidth);
return row * mColCount + col;
}
/**
* @return 单元格宽度
*/
public int getCellWidth() {
return (int) mCellWidth;
}
/**
* @return 单元格高度
*/
public int getCellHeight() {
return mCellHeight == -1 ? getResources().getDimensionPixelSize(R.dimen.home_item_height): mCellHeight;
}
/**
* @return 分割线宽度
*/
public int getDividerWidth() {
return (mIsDividerEnable ? mDividerWidth : 0);
}
/**
* @return 默认分割线宽度
*/
public int getDividerWidthDef() {
return mDividerWidth == -1 ? getResources().getDimensionPixelOffset(R.dimen.home_divider_width): mDividerWidth;
}
/**
* @return 总行数
*/
public int getRowCount() {
return mRowCount;
}
/**
* @return 总列数
*/
public int getColCount() {
return mColCount == -1 ? DEF_COLCOUNT : mColCount;
}
/**
* 改变子项的位置
*
* @param aFromPosition 原始位置
* @param aToPosition 终点位置
* @param isAnimation 是否附带动画效果
*/
public void changeItemPosition(int aFromPosition, int aToPosition, boolean isAnimation) {
int nChildCount = getIconViewCount();
// 边界判断
if ((aFromPosition < 0) || (aFromPosition >= nChildCount) || (aToPosition < 0)
|| (aToPosition >= nChildCount)) {
return;
}
// 寻找子项
View fromView = null;
View toView = null;
for (int i = 0; i < nChildCount; i++) {
View childView = getIconView(i);
if (getViewPosition(childView) == aFromPosition) {
fromView = childView;
}
if (getViewPosition(childView) == aToPosition) {
toView = childView;
}
}
// 设置新的项
if (fromView == toView) {
layoutItem(fromView, aToPosition, isAnimation);
} else {
layoutItem(fromView, aToPosition, isAnimation);
layoutItem(toView, aFromPosition, isAnimation);
}
}
/**
* @param aView 视图
* @return 视图位置
*/
public int getViewPosition(View aView) {
return getViewHolder(aView).getPosition();
}
/**
* @param aView 视图
* @param aPos 视图位置
*/
public void setViewPosition(View aView, int aPos) {
getViewHolder(aView).setPosition(aPos);
}
/**
* @param aView 视图
* @return holder
*/
private BdViewHolder getViewHolder(View aView) {
BdViewHolder holder = (BdViewHolder) aView.getTag(VIEW_HOLDER_TAG);
if (holder == null) {
holder = new BdViewHolder();
aView.setTag(VIEW_HOLDER_TAG, holder);
}
return holder;
}
/**
* 数据更新类
*/
class BdCellDataObserver extends DataSetObserver {
@Override
public void onChanged() {
super.onChanged();
// 刷新视图
refreshViews();
}
}
/**
* 视图holder
*/
class BdViewHolder {
/**
* 动画信息
*/
BdAnimInfo mAnimInfo;
/**
* 视图数据
*/
Object mViewData;
/**
* 视图位置
*/
int mViewPosition;
/**
* 构造函数
*/
public BdViewHolder() {
mAnimInfo = new BdAnimInfo();
}
/**
* @return 动画信息
*/
public BdAnimInfo getAnimInfo() {
return mAnimInfo;
}
/**
* @param aData 数据
*/
public void setData(Object aData) {
mViewData = aData;
}
/**
* @return 数据
*/
public Object getData() {
return mViewData;
}
/**
* @param aPosition 位置
*/
public void setPosition(int aPosition) {
mViewPosition = aPosition;
}
/**
* @return 位置
*/
public int getPosition() {
return mViewPosition;
}
}
/**
* 动画信息
*/
class BdAnimInfo {
/**
* 目标坐标
*/
int mStartX;
/**
* 目标坐标
*/
int mStartY;
/**
* 目标坐标
*/
int mTargetX;
/**
* 目标坐标
*/
private int mTargetY;
/**
* 目标坐标
*/
private int mMoveX;
/**
* 目标坐标
*/
private int mMoveY;
/**
* 目标坐标
*/
private boolean mIsMove;
/**
* 开始移动
*
* @param aStartX 起始x值
* @param aStartY 起始y值
* @param aTargetX 目标x值
* @param aTargetY 目标y值
*/
public void beginMove(int aStartX, int aStartY, int aTargetX, int aTargetY) {
setIsMove(true);
mTargetX = aTargetX;
mTargetY = aTargetY;
mStartX = aStartX;
mStartY = aStartY;
}
/**
* 结束移动
*/
public void endMove() {
setIsMove(false);
}
/**
* @param aFactor 比例
*/
public void move(float aFactor) {
if (isMove()) {
mMoveX = mStartX + (int) ((mTargetX - mStartX) * aFactor);
mMoveY = mStartY + (int) ((mTargetY - mStartY) * aFactor);
}
}
/**
* 设置是否在移动
*
* @param isMove 标志
*/
public void setIsMove(boolean isMove) {
mIsMove = isMove;
}
/**
* 判断是否在移动
*
* @return 判断结果
*/
public boolean isMove() {
return mIsMove;
}
/**
* @return x
*/
public int getX() {
return mMoveX;
}
/**
* @return y
*/
public int getY() {
return mMoveY;
}
}
}
BaseGridView
接着我们继续实现定义好的抽象类,完成上面必须实现的俩抽象方法:
此类的作用时将事件绑定到我们的宫格视图上,因此我抽象出了来方法:长按事件和短按事件,便于上层实现
/**
* 基础 baseGridview
* 提供事件绑定操作
* Created by LIUYONGKUI726 on 2016-03-03.
*/
public abstract class BaseGridView extends AbsGridView implements OnClickListener, OnLongClickListener{
/**
* @param context 上下文
* @param adapter 适配器
*/
public BaseGridView(Context context, PaAbsAdapter adapter) {
this(context, adapter, -1);
}
/**
* @param context 上下文
* @param adapter 适配器
* @param aCount 列数(默认3)
*/
public BaseGridView(Context context, PaAbsAdapter adapter, int aCount) {
super(context, adapter, aCount <= 0 ? -1 : aCount);
}
@Override
public void layoutItem(View aChild, int aPos, boolean aIsAnim) {
super.layoutItem(aChild, aPos, aIsAnim);
aChild.setOnClickListener(this);
aChild.setOnLongClickListener(this);
}
@Override
public void onClick(View v) {
onItemClick(v);
}
@Override
public boolean onLongClick(View v) {
return onItemLongClick(v);
}
/**
* item 短按事件
* @param v
*/
public abstract void onItemClick(View v);
/**
* item 长按事件
* @param v
*/
public abstract boolean onItemLongClick(View v);
}
AbsAdapter
接着我们还要写一个抽象泛型适配器,方便上层数据和view的绑定
public abstract class AbsAdapter<T> extends BaseAdapter {
public ArrayList<T> mList = new ArrayList<T>();
public Context mContext;
public PaAbsAdapter(Context context, List<T> list) {
mContext = context;
if (list != null) {
mList.addAll(list);
}
}
@Override
public int getCount() {
return mList == null ? 0 : mList.size();
}
@Override
public Object getItem(int position) {
return mList == null ? null : mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public abstract View getView(int position, View convertView, ViewGroup parent);
/**
* 更新ListView
*
* @param list
*/
public void notifyDataSetChanged(ArrayList<T> list) {
if (mList != list) {
mList.clear();
if (list != null) {
mList.addAll(list);
}
}
if (mContext != null) {
((Activity)mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
}
}
具体使用
PulginGridView
由于我的项目需要用这个做插件功能,而且微信中的钱包其实集成了很多插件,因此我将这个gridview命名为pluginView
**
* Created by liuyongkui on 2016-09-02.
*/
public class PluginGridview2 extends BaseGridView {
public PluginGridview2(Context context, PaAbsAdapter adapter, int aCount) {
super(context, adapter, aCount);
}
public PluginGridview2(Context context, PaAbsAdapter adapter) {
super(context, adapter);
}
@Override
public void onItemClick(View v) {
}
@Override
public boolean onItemLongClick(View v) {
return false;
}
}
PluginGridViewItem
一看标题你既知道这个gridview的item,这里我也没用xml;直接用纯java代码实现,直接继承RelativeLayout,看看了效果图,你就知道里面必定有个imageview和一行文字。
接着代码, 并且做了点击item的背景效果,
public class PluginGridViewItem extends RelativeLayout {
/** 完全不透明度 */
private static final int FULL_ALPHA = 255;
/** 半不透明度 */
private static final int HALF_ALPHA = 128;
/** icon名称文字的大小,单位dp */
private static final int SUG_NAME_TEXT_SIZE = 12;
/** ICON 视图的ID */
private static final int SUG_ICON_ID = 0x0101;
/** 数据 */
private PluginConfigModle mGuideModle;
/** ICON */
private ImageView mSugIcon;
/** 名称 */
private TextView mSugName;
/** 上下文 */
private Context mContext;
/** 图片加载器 */
private Picasso mImageLoader;
/** 该视图宽度 */
private int mWidth;
/** 该视图高度 */
private int mHeight;
/** ICON 的宽高 */
private int mIconWidth;
/** SUG 名称视图的高度 */
private int mTextHeight;
/** SUG 名称视图的宽度 */
private int mTextWidth;
/** 是否按下 */
private boolean mIsPressed;
/** 按下时的背景 */
private Drawable mCellPressDrawable;
/** 夜间模式下按下时的背景 */
private Drawable mNightCellPressDrawable;
/**
* 默认图片,使用static减少对象数
*/
private static Bitmap mDefaultIcon;
/** 点击事件监听器 */
private OnItemClickListener mItemClickListener;
private PluginGridViewItem(Context aContext, AttributeSet aAttrs) {
super(aContext, aAttrs);
}
private PluginGridViewItem(Context aContext) {
this(aContext, null);
}
public PluginGridViewItem(Context aContext,
PluginConfigModle aGuideModle, Picasso aImageLoader) {
this(aContext);
mGuideModle = aGuideModle;
mContext = aContext;
mImageLoader = aImageLoader;
init();
}
public PluginConfigModle getPluginModle() {
return mGuideModle;
}
public void setPluginModle(PluginConfigModle mGuideModle) {
this.mGuideModle = mGuideModle;
}
/***
* 检查屏幕的方向.
*
* @return false
*/
private boolean isLandscape() {
if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
return true;
} else {
return false;
}
}
/**
* 计算ICON的宽度,以及该视图的宽高
*/
private void initCellWidth() {
int screenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
if (screenWidth > screenHeight) {
int temp = screenWidth;
screenWidth = screenHeight;
screenHeight = temp;
}
if (isLandscape()) {
int m = PluginGridView.mColCount;
int n = m - 1;
int padding = (int) (PluginGridView.PADDING_LANDSCAPE * screenHeight / 1280);
int spacing = (int) (PluginGridView.ICON_SPACING_LANDSCAPE
* screenHeight / 1280);
mIconWidth = (screenHeight - n * spacing - 2 * padding) / m ;
spacing = (int) ((PluginGridView.ICON_SPACING_LANDSCAPE - PluginGridView.PADDING_LANDSCAPE)
* screenHeight / 1280);
mWidth = (screenHeight - n * spacing - padding) / m;
} else {
int m = PluginGridView.mColCount;
int n = m - 1;
int padding = (int) (PluginGridView.PADDING_PORTRAIT * screenWidth / 720);
int spacing = (int) (PluginGridView.ICON_SPACING_PORTRAIT
* screenWidth / 720);
mIconWidth = (screenWidth - n * spacing - 2 * padding) / m;
padding = (int) (PluginGridView.PADDING_PORTRAIT * screenWidth / 720) / 2;
spacing = 0;
mWidth = (screenWidth - padding * 2) / m;
}
}
/**
* 初始化视图
*/
private void init() {
mCellPressDrawable = getResources().getDrawable(
R.drawable.home_item_bg);
mNightCellPressDrawable = getResources().getDrawable(
R.drawable.home_item_night_bg);
mSugIcon = new ImageView(mContext);
//mSugIcon.setImageBitmap(mDefaultIcon);
//mSugIcon.setId(SUG_ICON_ID);
mSugIcon.setScaleType(ImageView.ScaleType.FIT_XY);
mSugIcon.setMaxHeight(mWidth);
mImageLoader.load(Uri.parse(mGuideModle.getAppIcon())).into(mSugIcon);
LayoutParams params = new LayoutParams(
mWidth, mWidth);
params.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
params.topMargin = mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_icon_top_margin);
this.addView(mSugIcon, params);
// init the name view
mSugName = new TextView(mContext);
mSugName.setMaxLines(1);
mSugName.setText(mGuideModle.getPluginName());
mSugName.setTextSize(TypedValue.COMPLEX_UNIT_SP, SUG_NAME_TEXT_SIZE);
mSugName.setGravity(Gravity.CENTER);
mSugName.setTextColor(mContext.getResources().getColor(
R.color.sug_item_name_color));
LayoutParams txtParams = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
txtParams.topMargin = mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_text_top_margin);
txtParams.bottomMargin = mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_text_bottom_margin);
txtParams.addRule(RelativeLayout.BELOW, SUG_ICON_ID);
txtParams
.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
mSugName.setLayoutParams(txtParams);
this.addView(mSugName);
mTextHeight = (int) (mSugName.getPaint().getFontMetrics().descent - mSugName
.getPaint().getFontMetrics().top);
mTextWidth = (int) mSugName.getPaint().measureText(
mGuideModle.getPluginName());
//onThemeChanged(0);
}
/**
* 设置点击事件监听器
*
* @param aClickListener
* 点击事件监听器
*/
public void setOnItemClickListener(OnItemClickListener aClickListener) {
mItemClickListener = aClickListener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
initCellWidth();
mHeight = mIconWidth
+ mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_icon_top_margin)
+ mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_text_top_margin)
+ mTextHeight
+ mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_text_bottom_margin);
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mSugIcon != null) {
mSugIcon.layout(
(mWidth - mIconWidth) / 2,
mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_icon_top_margin),
(mWidth + mIconWidth) / 2,
mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_icon_top_margin)
+ mIconWidth);
}
if (mSugName != null) {
int top = mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_icon_top_margin)
+ mIconWidth
+ mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_text_top_margin);
int bottom = mHeight
- mContext.getResources().getDimensionPixelSize(
R.dimen.sug_item_text_bottom_margin);
if (mTextWidth > mWidth) {
mSugName.layout(0, top, mWidth, bottom);
} else {
mSugName.layout((mWidth - mTextWidth) / 2, top,
(mWidth + mTextWidth) / 2, bottom);
}
}
}
/**
* 处理手势.
*
* @see android.view.View#onTouchEvent(MotionEvent)
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIsPressed = true;
if (mItemClickListener != null) {
mItemClickListener.onPressDown(PluginGridViewItem.this);
}
break;
case MotionEvent.ACTION_UP:
mIsPressed = false;
if (mItemClickListener != null) {
mItemClickListener.onClick(PluginGridViewItem.this,
mGuideModle);
}
break;
case MotionEvent.ACTION_CANCEL:
mIsPressed = false;
if (mItemClickListener != null) {
mItemClickListener.onPressUp(PluginGridViewItem.this);
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
/**
* Item点击监听.
*/
public interface OnItemClickListener {
/**
* 点击.
*/
void onClick(PluginGridViewItem v, PluginConfigModle model);
/**
* 按下.
*/
void onPressDown(PluginGridViewItem v);
/**
* 弹起.
*/
void onPressUp(PluginGridViewItem v);
}
public void setBackground() {
if (mIsPressed) {
setBackgroundDrawable(mCellPressDrawable);
} else {
setBackgroundDrawable(null);
}
}
activity
初始化我们的PluginGridView, ,并将我们的数据数据适配器设置给girdview, 并将data,和item set上
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = MainActivity.this;
mPicasso = Picasso.with(mContext);
init();
addContentView(mPluginsView, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
private void init() {
mModles = loadPluginData();
mPluginAdapter = new PluginAdapter(mContext, mPicasso, mModles);
mPluginsView = new PluginGridview2(mContext, mPluginAdapter, 3);
mPluginsView.setIsDividerEnable(true);
}
结束
通过上面四个步骤,我们就轻松的实现了类似微信的就九宫格效果,项目中图片有来自网络的,因此我其中使用了picasso做图片加载库,这段代码不做过渡介绍
谢谢阅读!
Android 高逼格纯代码实现类似微信钱包带分割线的GridView的更多相关文章
- 下拉tableView实现类似微信中带图的灰色背景
UIView *topView = [[UIView alloc]initWithFrame:CGRectMake(, -, ScreenWidth, )]; UIImageView *iconIma ...
- 打造一个高逼格的android开源项目——小白全攻略 (转)
转自:打造一个高逼格的android开源项目 小引子 在平时的开发过程中,我们经常会查阅很多的资料,最常参考的是 github 的开源项目.通常在项目的主页面能看到项目的简介和基本使用,并且时不时能看 ...
- iOS高仿app源码:纯代码打造高仿优质《内涵段子》
iOS高仿app源码:纯代码打造高仿优质<内涵段子>收藏下来 字数1950 阅读4999 评论173 喜欢133 Github 地址 https://github.com/Charlesy ...
- android高仿微信UI点击头像显示大图片效果
用过微信的朋友朋友都见过微信中点击对方头像显示会加载大图,先贴两张图片说明下: 这种UI效果对用户的体验不错,今天突然有了灵感,试着去实现,结果就出来了.. 下面说说我的思路: 1.点击图片时跳转到另 ...
- VopSdk一个高逼格微信公众号开发SDK:自动化生产(装逼模式开启)
VopSdk一个高逼格微信公众号开发SDK(源码下载) VopSdk一个高逼格微信公众号开发SDK:自动化生产(装逼模式开启) 针对第一版,我们搞了第二版本,老规矩先定个目标. 一 我们的目标 a.移 ...
- Android高级控件(五)——如何打造一个企业级应用对话列表,以QQ,微信为例
Android高级控件(五)--如何打造一个企业级应用对话列表,以QQ,微信为例 看标题这么高大上,实际上,还是运用我么拿到listview去扩展,我们讲什么呢,就是研究一下QQ,微信的这种对话列表, ...
- VopSdk一个高逼格微信公众号开发SDK(源码下载)
看之前回复很多说明大家很有热情&文章被误删掉了,不想让有的朋友错失这个高逼格的东西,现在重新发布,这次就直接放出源码,文章最末下载地址. 看之前回复很多说明大家很有热情&文章被误删掉了 ...
- Android中如何在代码中设置View的宽和高?
Android中如何在代码中设置View的宽和高?https://zhidao.baidu.com/question/536302117.htmlhttps://blog.csdn.net/u0141 ...
- android 实现类似微信缓存和即时更新好友头像
引言 使用微信时我们会发现,首次进入微信的好友列表时,会加载好友头像,但是再次进入时,就不用重新加载了,而且其他页面都不用重新加载,说明微信的好友头像是缓存在本地的,然后好友修改头像后,又会及时的更新 ...
随机推荐
- RxJava操作符(03-变换操作)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51649975 本文出自:[openXu的博客] 目录: Buffer FlatMap fla ...
- React Native实现一个自定义模块
概述 在 前期介绍React Native 项目结构的时候,我们讲解过React的项目组成,其中说过 node_modules 文件夹,这是一个存放 node 模块的地方.我们知道React是用npm ...
- 早期Swift中Cocos2D初始化代码的重构
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 我们知道在早期的Swift中在子类里只能调用超类的design ...
- 在非ViewController中显示AlertController的方法
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 以前我们可以在任何类中使用UIAlertView的show实例 ...
- linux shell 判断文件是否存在等符号
-a file exists. -b file exists and is a block special file. -c file exists and is a character spec ...
- mysql 字符集查看 设定
(1) 最简单的修改方法,就是修改mysql的my.ini文件中的字符集键值, 如 default-character-set = utf8 character_set_server = utf8 修 ...
- python的subprocess:子程序调用(调用执行其他命令);获取子程序脚本当前路径问题
python当前进程可以调用子进程,子进程可以执行其他命令,如shell,python,java,c... 而调用子进程方法有 os模块 参见:http://blog.csdn.net/longshe ...
- Cocos2D iOS之旅:如何写一个敲地鼠游戏(七):弹出地鼠
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...
- 8.非关系型数据库(Nosql)之mongodb的应用场景
测试脚本: Mysql测试脚本: [php] view plaincopyprint? 1. <?php 2. header("Content-Type:text/html; ...
- Qualcomm平台camera调试移植入门
1 camera基本代码架构 高通平台对于camera的代码组织,大体上还是遵循Android的框架:即上层应用和HAL层交互,高通平台在HAL层里面实现自己的一套管理策略:在kernel中实现se ...