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

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

Java MCP 的鉴权深度解析

《JavaMCP的鉴权深度解析》文章介绍JavaMCP鉴权的实现方式,指出客户端可通过queryString、header或env传递鉴权信息,服务器端支持工具单独鉴权、过滤器集中鉴权及启动时鉴权... 目录一、MCP Client 侧(负责传递,比较简单)(1)常见的 mcpServers json 配置

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱

Maven中生命周期深度解析与实战指南

《Maven中生命周期深度解析与实战指南》这篇文章主要为大家详细介绍了Maven生命周期实战指南,包含核心概念、阶段详解、SpringBoot特化场景及企业级实践建议,希望对大家有一定的帮助... 目录一、Maven 生命周期哲学二、default生命周期核心阶段详解(高频使用)三、clean生命周期核心阶

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Java Scanner类解析与实战教程

《JavaScanner类解析与实战教程》JavaScanner类(java.util包)是文本输入解析工具,支持基本类型和字符串读取,基于Readable接口与正则分隔符实现,适用于控制台、文件输... 目录一、核心设计与工作原理1.底层依赖2.解析机制A.核心逻辑基于分隔符(delimiter)和模式匹

Java+AI驱动实现PDF文件数据提取与解析

《Java+AI驱动实现PDF文件数据提取与解析》本文将和大家分享一套基于AI的体检报告智能评估方案,详细介绍从PDF上传、内容提取到AI分析、数据存储的全流程自动化实现方法,感兴趣的可以了解下... 目录一、核心流程:从上传到评估的完整链路二、第一步:解析 PDF,提取体检报告内容1. 引入依赖2. 封装