前言
通过上两篇关于自定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做图片加载库,这段代码不做过渡介绍
谢谢阅读!