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

相关文章

Python进阶之Excel基本操作介绍

《Python进阶之Excel基本操作介绍》在现实中,很多工作都需要与数据打交道,Excel作为常用的数据处理工具,一直备受人们的青睐,本文主要为大家介绍了一些Python中Excel的基本操作,希望... 目录概述写入使用 xlwt使用 XlsxWriter读取修改概述在现实中,很多工作都需要与数据打交

使用Python实现批量访问URL并解析XML响应功能

《使用Python实现批量访问URL并解析XML响应功能》在现代Web开发和数据抓取中,批量访问URL并解析响应内容是一个常见的需求,本文将详细介绍如何使用Python实现批量访问URL并解析XML响... 目录引言1. 背景与需求2. 工具方法实现2.1 单URL访问与解析代码实现代码说明2.2 示例调用

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

在C#中合并和解析相对路径方式

《在C#中合并和解析相对路径方式》Path类提供了几个用于操作文件路径的静态方法,其中包括Combine方法和GetFullPath方法,Combine方法将两个路径合并在一起,但不会解析包含相对元素... 目录C#合并和解析相对路径System.IO.Path类幸运的是总结C#合并和解析相对路径对于 C

Java解析JSON的六种方案

《Java解析JSON的六种方案》这篇文章介绍了6种JSON解析方案,包括Jackson、Gson、FastJSON、JsonPath、、手动解析,分别阐述了它们的功能特点、代码示例、高级功能、优缺点... 目录前言1. 使用 Jackson:业界标配功能特点代码示例高级功能优缺点2. 使用 Gson:轻量

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines