本文主要是介绍Android进阶 -- 知乎Matisse源码解析(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
这篇博客来重点关注Matisse里的自定义View,在MatisseActivity里,看到的逻辑十分流畅,是因为将细节部分都已经封装到了这些不同的组件中了,接下来一起看看这些组件的具体实现。
一、AlbumSpinner
AlbumSpinner用来在MatisseActivity里实现不同媒体文件夹的选取,使用ListPopupWindow作为UI显示,使用AlbumsAdapter返回的Cursor作为数据源。
1.数据源
AlbumSpinner里的setAdapter方法,传入了一个CursorAdapter
public void setAdapter(CursorAdapter adapter) {mListPopupWindow.setAdapter(adapter);mAdapter = adapter;}
在MatisseActivity里,传入了这个Adapter
mAlbumsSpinner.setAdapter(mAlbumsAdapter);
mAlbumsAdapter是AlbumsAdapter的实例,AlbumsAdapter继承自CursorAdapter。
2.UI显示
mListPopupWindow = new ListPopupWindow(context, null, R.attr.listPopupWindowStyle);mListPopupWindow.setModal(true);float density = context.getResources().getDisplayMetrics().density;mListPopupWindow.setContentWidth((int) (216 * density));mListPopupWindow.setHorizontalOffset((int) (16 * density));mListPopupWindow.setVerticalOffset((int) (-48 * density));
3.交互
交互主要集中在对于资源目录的点击事件,在AlbumSpinner里处理了一部分,然后通过onItemSelectedListener将点击事件回调给MatisseActivity,在上一篇中提到过这个接口。
mListPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {AlbumsSpinner.this.onItemSelected(parent.getContext(), position);if (mOnItemSelectedListener != null) {mOnItemSelectedListener.onItemSelected(parent, view, position, id);}}});
private void onItemSelected(Context context, int position) {mListPopupWindow.dismiss();Cursor cursor = mAdapter.getCursor();cursor.moveToPosition(position);Album album = Album.valueOf(cursor);String displayName = album.getDisplayName(context);if (mSelected.getVisibility() == View.VISIBLE) {mSelected.setText(displayName);} else {if (Platform.hasICS()) {mSelected.setAlpha(0.0f);mSelected.setVisibility(View.VISIBLE);mSelected.setText(displayName);mSelected.animate().alpha(1.0f).setDuration(context.getResources().getInteger(android.R.integer.config_longAnimTime)).start();} else {mSelected.setVisibility(View.VISIBLE);mSelected.setText(displayName);}}}
Album是文件夹的实体类,封装了文件夹的名字、封面图片等信息。
二、CheckView
CheckView继承自View,在onMeasure里设置了宽高,使其为正方形
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// fixed size 48dp x 48dpint sizeSpec = MeasureSpec.makeMeasureSpec((int) (SIZE * mDensity), MeasureSpec.EXACTLY);super.onMeasure(sizeSpec, sizeSpec);}
重点关注onDraw方法
@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// draw outer and inner shadowinitShadowPaint();canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,(STROKE_RADIUS + STROKE_WIDTH / 2 + SHADOW_WIDTH) * mDensity, mShadowPaint);// draw white strokecanvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,STROKE_RADIUS * mDensity, mStrokePaint);// draw contentif (mCountable) {if (mCheckedNum != UNCHECKED) {initBackgroundPaint();canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,BG_RADIUS * mDensity, mBackgroundPaint);initTextPaint();String text = String.valueOf(mCheckedNum);int baseX = (int) (canvas.getWidth() - mTextPaint.measureText(text)) / 2;int baseY = (int) (canvas.getHeight() - mTextPaint.descent() - mTextPaint.ascent()) / 2;canvas.drawText(text, baseX, baseY, mTextPaint);}} else {if (mChecked) {initBackgroundPaint();canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,BG_RADIUS * mDensity, mBackgroundPaint);mCheckDrawable.setBounds(getCheckRect());mCheckDrawable.draw(canvas);}}// enable hintsetAlpha(mEnabled ? 1.0f : 0.5f);}
首先初始化阴影画笔
private void initShadowPaint() {if (mShadowPaint == null) {mShadowPaint = new Paint();mShadowPaint.setAntiAlias(true);// all in dpfloat outerRadius = STROKE_RADIUS + STROKE_WIDTH / 2;float innerRadius = outerRadius - STROKE_WIDTH;float gradientRadius = outerRadius + SHADOW_WIDTH;float stop0 = (innerRadius - SHADOW_WIDTH) / gradientRadius;float stop1 = innerRadius / gradientRadius;float stop2 = outerRadius / gradientRadius;float stop3 = 1.0f;mShadowPaint.setShader(new RadialGradient((float) SIZE * mDensity / 2,(float) SIZE * mDensity / 2,gradientRadius * mDensity,new int[]{Color.parseColor("#00000000"), Color.parseColor("#0D000000"),Color.parseColor("#0D000000"), Color.parseColor("#00000000")},new float[]{stop0, stop1, stop2, stop3},Shader.TileMode.CLAMP));}}
接着在画上内阴影和外阴影
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,(STROKE_RADIUS + STROKE_WIDTH / 2 + SHADOW_WIDTH) * mDensity, mShadowPaint);
接着在描边
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2,STROKE_RADIUS * mDensity, mStrokePaint);
最后在填上内容
三、SquareFrameLayout
是一个继承自FrameLayout的自定义ViewGroup,保证为正方形
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, widthMeasureSpec);}
四、MediaGridInset
继承自RecyclerView.ItemDecoration,用来自定义RecyclerView的分割线,保证各个方向的间距一样
@Overridepublic void getItemOffsets(Rect outRect, View view, RecyclerView parent,RecyclerView.State state) {int position = parent.getChildAdapterPosition(view); // item positionint column = position % mSpanCount; // item columnif (mIncludeEdge) {// spacing - column * ((1f / spanCount) * spacing)outRect.left = mSpacing - column * mSpacing / mSpanCount;// (column + 1) * ((1f / spanCount) * spacing)outRect.right = (column + 1) * mSpacing / mSpanCount;if (position < mSpanCount) { // top edgeoutRect.top = mSpacing;}outRect.bottom = mSpacing; // item bottom} else {// column * ((1f / spanCount) * spacing)outRect.left = column * mSpacing / mSpanCount;// spacing - (column + 1) * ((1f / spanCount) * spacing)outRect.right = mSpacing - (column + 1) * mSpacing / mSpanCount;if (position >= mSpanCount) {outRect.top = mSpacing; // item top}}}
五、PreviewViewPager
继承自ViewPager,用来处理滑动冲突问题,如果是ImageViewTouch的事件实例,就先交给ImageViewTouch处理
@Overrideprotected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {if (v instanceof ImageViewTouch) {return ((ImageViewTouch) v).canScroll(dx) || super.canScroll(v, checkV, dx, x, y);}return super.canScroll(v, checkV, dx, x, y);}
}
六、RoundedRectangleImageView
圆角矩形类,继承自AppCompatImageView。
在onMeasure里确定了范围
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mRectF.set(0.0f, 0.0f, getMeasuredWidth(), getMeasuredHeight());mRoundedRectPath.addRoundRect(mRectF, mRadius, mRadius, Path.Direction.CW);}
在onDraw里进行裁剪
@Overrideprotected void onDraw(Canvas canvas) {canvas.clipPath(mRoundedRectPath);super.onDraw(canvas);}
七、MediaGrid
MediaGrid就是在图片选择界面看到的一个个图片的容器,它继承自上面提到的SquareFrameLayout,属性有缩略图、CheckView等一些列可见组件,同时进行了高度的封装,在Adapter里的调用更加方便。
public class MediaGrid extends SquareFrameLayout implements View.OnClickListener {private ImageView mThumbnail;private CheckView mCheckView;private ImageView mGifTag;private TextView mVideoDuration;private Item mMedia;private PreBindInfo mPreBindInfo;private OnMediaGridClickListener mListener;public MediaGrid(Context context) {super(context);init(context);}public MediaGrid(Context context, AttributeSet attrs) {super(context, attrs);init(context);}private void init(Context context) {LayoutInflater.from(context).inflate(R.layout.media_grid_content, this, true);mThumbnail = (ImageView) findViewById(R.id.media_thumbnail);mCheckView = (CheckView) findViewById(R.id.check_view);mGifTag = (ImageView) findViewById(R.id.gif);mVideoDuration = (TextView) findViewById(R.id.video_duration);mThumbnail.setOnClickListener(this);mCheckView.setOnClickListener(this);}@Overridepublic void onClick(View v) {if (mListener != null) {if (v == mThumbnail) {mListener.onThumbnailClicked(mThumbnail, mMedia, mPreBindInfo.mViewHolder);} else if (v == mCheckView) {mListener.onCheckViewClicked(mCheckView, mMedia, mPreBindInfo.mViewHolder);}}}public void preBindMedia(PreBindInfo info) {mPreBindInfo = info;}public void bindMedia(Item item) {mMedia = item;setGifTag();initCheckView();setImage();setVideoDuration();}public Item getMedia() {return mMedia;}private void setGifTag() {mGifTag.setVisibility(mMedia.isGif() ? View.VISIBLE : View.GONE);}private void initCheckView() {mCheckView.setCountable(mPreBindInfo.mCheckViewCountable);}public void setCheckEnabled(boolean enabled) {mCheckView.setEnabled(enabled);}public void setCheckedNum(int checkedNum) {mCheckView.setCheckedNum(checkedNum);}public void setChecked(boolean checked) {mCheckView.setChecked(checked);}private void setImage() {if (mMedia.isGif()) {SelectionSpec.getInstance().imageEngine.loadGifThumbnail(getContext(), mPreBindInfo.mResize,mPreBindInfo.mPlaceholder, mThumbnail, mMedia.getContentUri());} else {SelectionSpec.getInstance().imageEngine.loadThumbnail(getContext(), mPreBindInfo.mResize,mPreBindInfo.mPlaceholder, mThumbnail, mMedia.getContentUri());}}private void setVideoDuration() {if (mMedia.isVideo()) {mVideoDuration.setVisibility(VISIBLE);mVideoDuration.setText(DateUtils.formatElapsedTime(mMedia.duration / 1000));} else {mVideoDuration.setVisibility(GONE);}}public void setOnMediaGridClickListener(OnMediaGridClickListener listener) {mListener = listener;}public void removeOnMediaGridClickListener() {mListener = null;}public interface OnMediaGridClickListener {void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView.ViewHolder holder);void onCheckViewClicked(CheckView checkView, Item item, RecyclerView.ViewHolder holder);}public static class PreBindInfo {int mResize;Drawable mPlaceholder;boolean mCheckViewCountable;RecyclerView.ViewHolder mViewHolder;public PreBindInfo(int resize, Drawable placeholder, boolean checkViewCountable,RecyclerView.ViewHolder viewHolder) {mResize = resize;mPlaceholder = placeholder;mCheckViewCountable = checkViewCountable;mViewHolder = viewHolder;}}}
通过这篇博客,对于Matisse的UI控件部分就比较熟悉了,下一篇博客,一起去看数据的加载机制。
这篇关于Android进阶 -- 知乎Matisse源码解析(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!