我是靠谱客的博主 眼睛大夏天,这篇文章主要介绍Android进阶 -- 知乎Matisse源码解析(三),现在分享给大家,希望可以做个参考。

这篇博客来重点关注Matisse里的自定义View,在MatisseActivity里,看到的逻辑十分流畅,是因为将细节部分都已经封装到了这些不同的组件中了,接下来一起看看这些组件的具体实现。

一、AlbumSpinner

AlbumSpinner用来在MatisseActivity里实现不同媒体文件夹的选取,使用ListPopupWindow作为UI显示,使用AlbumsAdapter返回的Cursor作为数据源。

1.数据源

AlbumSpinner里的setAdapter方法,传入了一个CursorAdapter

复制代码
1
2
3
4
public void setAdapter(CursorAdapter adapter) { mListPopupWindow.setAdapter(adapter); mAdapter = adapter; }

在MatisseActivity里,传入了这个Adapter

复制代码
1
mAlbumsSpinner.setAdapter(mAlbumsAdapter);

mAlbumsAdapter是AlbumsAdapter的实例,AlbumsAdapter继承自CursorAdapter。

2.UI显示

复制代码
1
2
3
4
5
6
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,在上一篇中提到过这个接口。

复制代码
1
2
3
4
5
6
7
8
9
mListPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public 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); } } });
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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里设置了宽高,使其为正方形

复制代码
1
2
3
4
5
6
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // fixed size 48dp x 48dp int sizeSpec = MeasureSpec.makeMeasureSpec((int) (SIZE * mDensity), MeasureSpec.EXACTLY); super.onMeasure(sizeSpec, sizeSpec); }

重点关注onDraw方法

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // draw outer and inner shadow initShadowPaint(); canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2, (STROKE_RADIUS + STROKE_WIDTH / 2 + SHADOW_WIDTH) * mDensity, mShadowPaint); // draw white stroke canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2, STROKE_RADIUS * mDensity, mStrokePaint); // draw content if (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 hint setAlpha(mEnabled ? 1.0f : 0.5f); }

首先初始化阴影画笔

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void initShadowPaint() { if (mShadowPaint == null) { mShadowPaint = new Paint(); mShadowPaint.setAntiAlias(true); // all in dp float 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)); } }

接着在画上内阴影和外阴影

复制代码
1
2
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2, (STROKE_RADIUS + STROKE_WIDTH / 2 + SHADOW_WIDTH) * mDensity, mShadowPaint);

接着在描边

复制代码
1
2
canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2, STROKE_RADIUS * mDensity, mStrokePaint);

最后在填上内容

 

三、SquareFrameLayout

是一个继承自FrameLayout的自定义ViewGroup,保证为正方形

复制代码
1
2
3
4
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, widthMeasureSpec); }

四、MediaGridInset

继承自RecyclerView.ItemDecoration,用来自定义RecyclerView的分割线,保证各个方向的间距一样

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = parent.getChildAdapterPosition(view); // item position int column = position % mSpanCount; // item column if (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 edge outRect.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处理

复制代码
1
2
3
4
5
6
7
8
@Override protected 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里确定了范围

复制代码
1
2
3
4
5
6
@Override protected 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里进行裁剪

复制代码
1
2
3
4
5
@Override protected void onDraw(Canvas canvas) { canvas.clipPath(mRoundedRectPath); super.onDraw(canvas); }

 

七、MediaGrid

MediaGrid就是在图片选择界面看到的一个个图片的容器,它继承自上面提到的SquareFrameLayout,属性有缩略图、CheckView等一些列可见组件,同时进行了高度的封装,在Adapter里的调用更加方便。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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); } @Override public 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源码解析(三)的全部内容,更多相关Android进阶内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(68)

评论列表共有 0 条评论

立即
投稿
返回
顶部