Android进阶 -- 知乎Matisse源码解析(三)

2023-12-24 12:18

本文主要是介绍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源码解析(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/531745

相关文章

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Java Spring ApplicationEvent 代码示例解析

《JavaSpringApplicationEvent代码示例解析》本文解析了Spring事件机制,涵盖核心概念(发布-订阅/观察者模式)、代码实现(事件定义、发布、监听)及高级应用(异步处理、... 目录一、Spring 事件机制核心概念1. 事件驱动架构模型2. 核心组件二、代码示例解析1. 事件定义

CSS place-items: center解析与用法详解

《CSSplace-items:center解析与用法详解》place-items:center;是一个强大的CSS简写属性,用于同时控制网格(Grid)和弹性盒(Flexbox)... place-items: center; 是一个强大的 css 简写属性,用于同时控制 网格(Grid) 和 弹性盒(F

python常见环境管理工具超全解析

《python常见环境管理工具超全解析》在Python开发中,管理多个项目及其依赖项通常是一个挑战,下面:本文主要介绍python常见环境管理工具的相关资料,文中通过代码介绍的非常详细,需要的朋友... 目录1. conda2. pip3. uvuv 工具自动创建和管理环境的特点4. setup.py5.

全面解析HTML5中Checkbox标签

《全面解析HTML5中Checkbox标签》Checkbox是HTML5中非常重要的表单元素之一,通过合理使用其属性和样式自定义方法,可以为用户提供丰富多样的交互体验,这篇文章给大家介绍HTML5中C... 在html5中,Checkbox(复选框)是一种常用的表单元素,允许用户在一组选项中选择多个项目。本

Python包管理工具核心指令uvx举例详细解析

《Python包管理工具核心指令uvx举例详细解析》:本文主要介绍Python包管理工具核心指令uvx的相关资料,uvx是uv工具链中用于临时运行Python命令行工具的高效执行器,依托Rust实... 目录一、uvx 的定位与核心功能二、uvx 的典型应用场景三、uvx 与传统工具对比四、uvx 的技术实

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据