《极简笔记》源码分析
Posted 吴豪杰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《极简笔记》源码分析相关的知识,希望对你有一定的参考价值。
0. 介绍
此文将对Github上lguipeng大神所开发的 极简笔记 v2.0
(点我下载源码)代码进行分析学习。
通过此文你将学到:
- 应用源码的研读方法
- MVP架构模式
- Application的应用
- Degger2依赖注入框架
- 搜索控件的使用
- ButterKnife库的使用
- Material主题
- RecyclerView等新控件的用法
- Lambda表达式
- Java自定义注解
- aFinal框架
1. Manifest入手
1.1 权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
声明了网络与储存读写相关权限,至于网络权限笔者猜测应该是用于印象笔记的同步吧。
1.2 Application层
android:name=".App"
在Application层发现了一个奇怪的属性,然后又发现项目结构目录中有个继承自Application的类,顿时疑惑。经查阅后又联想到包建强的《App研发录》中提到彻底结束安卓程序进程需要用到继承Application的类来记录已经打开的Activity,然后统一结束它们,如代码所示:
public class App extends Application
public List<Activity> activities=new ArrayList<Activity>();
Manifest进行注册:
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:name=".App" >
每个Activity中的做法如下:
//首先:onCreate()方法里边:
App app = (App) getApplicationContext();// 获取应用程序全局的实例引用
app.activities.add(this); // 把当前Activity放入集合中
//然后:onDestroy()方法里边做法:
@Override
protected void onDestroy()
super.onDestroy();
App app = (App) getApplication();// 获取应用程序全局的实例引用
app.activities.remove(this); // 把当前Activity从集合中移除
//最后:在程序中需要结束时的做法:
List<Activity> activities = app.activities;
for (Activity act : activities)
act.finish();// 显式结束
我想此处亦是同样原理。
补充Application相关知识点:
- 创建一个类继承Application并在manifest的application标签中进行注册
- 生命周期等于这个程序的生命周期
- 通常用于数据传递、数据共享、数据缓存等操作
- onTerminate() 当终止应用程序对象时调用 onLowMemory() 当后台程序已经终止资源还匮乏时会调用
1.2.1 探索继承自Application的App类
类中定义了以下方法:
private void initializeInjector()
mAppComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
通过DaggerAppComponent可以发现使用了Dagger2库,那么Dagger库又是什么呢?继续探索…
1.2.1.1 Dagger2介绍
在此之前,需要先了解依赖注入,在本人看来其实就是低级类对高级类的依赖关系,它有以下好处:
- 依赖的注入和配置独立于组件之外
- 因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库
- 依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单
而Dagger2就是Google基于java的依赖注入标准维护的一个库。
1.2.1.1 Dagger2的使用
第一步: 添加编译和运行库
dependencies
apt 'com.google.dagger:dagger-compiler:2.0'
compile 'com.google.dagger:dagger:2.0'
...
第二步: 构建依赖
@Module
public class ActivityModule
@Provides UserModel provideUserModel()
return new UserModel();
第三步: 构建Injector
@Component(modules = ActivityModule.class)
public interface ActivityComponent
void inject(MainActivity activity);
第三步: 完成依赖注入
public class MainActivity extends ActionBarActivity
private ActivityComponent mActivityComponent;
@Inject UserModel userModel;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mActivityComponent = DaggerActivityComponent.builder().activityModule(new ActivityModule()).build();
mActivityComponent.inject(this);
((TextView) findViewById(R.id.user_desc_line)).setText(userModel.id + "\\n" + userModel.name + "\\n" + userModel.gender);
...
1.3 Activity层
<activity
android:name=".ui.MainActivity"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize|stateHidden"
android:screenOrientation="portrait">
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
由标签里的内容可以看出该Activity是程序启动的主Activity,如图:
此外,还有一点值得注意:
1.3.1 搜索功能的使用方法
搜索有两种实现方式,默认搜索框(比如Toolbar上面的)和搜索控件(可以在Layout里面声明的SearchView),一般采用默认的搜索框方式即可,此处也只简单讲讲此方式,如要了解更多可以去阅读官方文档的创建搜索界面
1.3.1.1 创建搜索配置文件
主要是对搜索框样式的配置,文件保存在res/xml/searchable.xml
:
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_label"
android:hint="@string/search_hint" >
</searchable>
1.3.1.2 创建Activity并注册
注册Activity有两个要点,一个是接收Intent.ACTION_SEARCH,另一个是搜索框的配置文件地址:
<application ... >
<activity android:name=".SearchableActivity" >
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
...
</application>
1.3.1.3 执行搜索过程
搜索的执行过程又分为3步:
- 接收查询: 收到Intent数据获取到搜索内容执行搜索
- 搜索你的资料: 通过SQLite的FTS3方式搜索或进行在线搜索
- 呈现结果: 使用ListView等展示结果
此处展示接收查询的示例代码:
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.search);
// Get the intent, verify the action and get the query
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction()))
String query = intent.getStringExtra(SearchManager.QUERY);
doMySearch(query);
1.3.1.4 进行实时搜索
如果要进行实时搜索,需要在Activity中重写onSearchRequested()方法,返回true代表成功消耗此请求,示例代码如下:
@Override
public boolean onSearchRequested()
Bundle appData = new Bundle();
appData.putBoolean(SearchableActivity.JARGON, true);
startSearch(null, false, appData, false);
return true;
// startSearch()中
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData != null)
boolean jargon = appData.getBoolean(SearchableActivity.JARGON);
2. 攻入MainActivity
2.1 ButterKnife
public class MainActivity extends BaseActivity implements MainView
@Bind(R.id.toolbar) Toolbar toolbar;
@Bind(R.id.refresher) SwipeRefreshLayout refreshLayout;
...
打开MainActivity。映入眼帘的是熟悉的ButterKnife,此处回顾一下ButterKnife的使用。
2.1.1 使用方法
- 导库
下载jar包导入或者直接在gradle中加上compile 'com.jakewharton:butterknife:7.0.1'
即可 @Bind
和ButterKnife.bind(Activity act);
看如下一段代码就能明白如何使用:
class ExampleActivity extends Activity
@Bind(R.id.user) EditText username;
@Bind(R.id.pass) EditText password;
@BindString(R.string.login_error)
String loginErrorMessage;
@OnClick(R.id.submit) void submit()
// TODO call server...
@Override public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
更多使用方法详见官方介绍,JakeWharton/butterknife
2.2 基类和接口
public class MainActivity extends BaseActivity implements MainView
...
从这里,可以进入基类BaseActivity和接口MainView看看。
2.2.1 重写Activity生命周期的BaseActivity
@Override
protected void onCreate(Bundle savedInstanceState)
parseIntent(getIntent());
showActivityInAnim();
initTheme();
super.onCreate(savedInstanceState);
initWindow();
initializeDependencyInjector();
setContentView(getLayoutView());
ButterKnife.bind(this);
initToolbar();
通过这样重写生命周期的方式可以使代码更加统一,便于后期管理和维护。
下面就简单分析几个方法:
2.2.1.1 处理数据
通过 parseIntent(getIntent());
处理传递到Activity的数据,可以进行一些初始化操作。
2.2.1.2 过渡动画
回顾一下Activity过渡动画的使用方法:
overridePendingTransition(R.anim.activity_down_up_anim, R.anim.activity_exit_anim);
xml中定义的动画:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator" >
<translate
android:duration="@android:integer/config_shortAnimTime"
android:fromYDelta="10%p"
android:toYDelta="0" />
</set>
两点注意:
- 此处笔者测试了下,即便
android:fromYDelta="100%p"
中为100%p,也不能省略为p。- 窗体过渡动画不一定要在setContentView之前执行,可以在onCreate()中任意位置执行
2.2.1.3 主题切换
主题切换是通过Activity中继承自ContextThemeWrapper的setTheme(int resid)方法实现的。
int style = R.style.RedTheme;
activity.setTheme(style);
styles中定义了多种样式:
<style name="RedTheme" parent="AppBaseTheme.Dark">
<item name="colorPrimary">@color/red</item>
<item name="colorPrimaryDark">@color/dark_red</item>
<item name="colorAccent">@color/accent_red</item>
</style>
2.2.1.4 针对KitKat的状态栏”沉浸模式”
@TargetApi(19)
private void initWindow()
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
SystemBarTintManager tintManager = new SystemBarTintManager(this);
tintManager.setStatusBarTintColor(getStatusBarColor());
tintManager.setStatusBarTintEnabled(true);
针对安卓4.4系统,通过使用 SystemBarTintManager
开源库实现了状态栏变色功能。
2.2.1.5 视图初始化
通过 setContentView(getLayoutView());
也巧妙将布局设置转移给子类实现 getLayoutView()
抽象方法。
@Override
protected int getLayoutView() return R.layout.activity_main;
通过这里我们也就又发现了新大陆,哦不,新道路,通往Activity布局文件的道路。
2.2.1.6 Toolbar初始化
由于各Activity中toolbar都一样,所以这里就将其抽取出来了,布局文件中使用 <include>
标签抽取,Activity中抽取出来一个ToolbarUtils类。
public class ToolbarUtils
public static void initToolbar(Toolbar toolbar, AppCompatActivity activity)
if (toolbar == null || activity == null)
return;
if (activity instanceof BaseActivity)
toolbar.setBackgroundColor(((BaseActivity) activity).getColorPrimary());
else
toolbar.setBackgroundColor(activity.getResources().getColor(R.color.toolbar_bg_color));
toolbar.setTitle(R.string.app_name);
toolbar.setTitleTextColor(activity.getResources().getColor(R.color.toolbar_title_color));
toolbar.collapseActionView();
activity.setSupportActionBar(toolbar);
if (activity.getSupportActionBar() != null)
activity.getSupportActionBar().setHomeAsUpIndicator(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
2.2.1.7 重启Activity
BaseActivity中还包含一个reload()方法,用于没有动画的重启自身Activity,以便应用新的主题。关于不重启应用新样式主题,读者感兴趣可以去了解知乎的不重启Activity切换主题解决方案。
public void reload(boolean anim)
Intent intent = getIntent();
if (!anim)
overridePendingTransition(0, 0);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
intent.putExtra(BaseActivity.IS_START_ANIM, false);
finish();
if (!anim)
overridePendingTransition(0, 0);
startActivity(intent);
至此,BaseActivity分析得差不多了,接下来回到MainActivity。
2.2.2 MainView接口
回到MainActivity再看看MainView接口,此接口主要是对BaseActivity里的共有方法进行抽象。
public interface MainView extends View
void initToolbar();
void initDrawerView(List<String> list);
void setToolbarTitle(String title);
void showProgressWheel(boolean visible);
void switchNoteTypePage(List<SNote> notes);
void addNote(SNote note);
...
注意View接口是在本项目中的接口,而非android.view.View
2.3 MainPresenter桥梁
大致浏览MainActivity,可以看到到处都是MainPresenter的影子,这便是MVP的架构思想,在MainActivity中将逻辑操作转交给MainPresenter去执行。
// 初始化依赖注入
@Override
protected void initializeDependencyInjector()
App app = (App) getApplication();
mActivityComponent = DaggerActivityComponent.builder()
.activityModule(new ActivityModule(this))
.appComponent(app.getAppComponent())
.build();
mActivityComponent.inject(this);
那么显然在MainActivity分析完成后的下一个目标就是MainPresenter了,现在先不急,继续分析MainActivity。
2.4 onCreate()的重写
在MainActivity中,并没有使用BaseActivity重写的生命周期,而是再次重写onCreate()方法,以独具一格。
@Override
protected void onCreate(Bundle savedInstanceState)
launchWithNoAnim();
super.onCreate(savedInstanceState);
initializePresenter();
mainPresenter.onCreate(savedInstanceState);
2.5 主布局文件分析
通过 getLayoutView()
可以找到主Activity对应的布局文件。主布局由ToolBar和DrawerLayout组成,DrawerLayout中包含RecyclerView正文界面和ListView侧滑界面,为了更好兼容低版本安卓系统,使低版本也能够拥有5.0以上版本的特效,大量使用了第三方库和自定义控件。
2.5.1 头声明
此处注意xmlns多个是可以省略为一个的,并不会影响程序的执行,但为了代码的可读性,还是应该写成多个。
xmlns:fab="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:wheel="http://schemas.android.com/apk/res-auto"
2.5.2 FixedRecyclerView
<com.lguipeng.notes.view.FixedRecyclerView
android:id="@+id/recyclerView"
android:padding="4dp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
这个是作者的一个修正后的RecyclerView控件。
public class FixedRecyclerView extends RecyclerView
...
@Override
public boolean canScrollVertically(int direction)
// check if scrolling up
if (direction < 1)
boolean original = super.canScrollVertically(direction);
return !original && getChildAt(0) != null && getChildAt(0).getTop() < 0 || original;
return super.canScrollVertically(direction);
这段代码暂时有些难以理解,此处就不详细分析了。此处读者可以去回顾RecyclerView的用法。在MainActivity中,对RecyclerView进行了初始化:
@Override
public void initRecyclerView(List<SNote> notes)
recyclerAdapter = new NotesAdapter(notes, this);
recyclerView.setHasFixedSize(true);
recyclerAdapter.setOnInViewClickListener(R.id.notes_item_root,
new BaseRecyclerViewAdapter.onInternalClickListenerImpl<SNote>()
@Override
public void OnClickListener(View parentV, View v, Integer position, SNote values)
super.OnClickListener(parentV, v, position, values);
mainPresenter.onRecyclerViewItemClick(position, values);
);
recyclerAdapter.setOnInViewClickListener(R.id.note_more,
new BaseRecyclerViewAdapter.onInternalClickListenerImpl<SNote>()
@Override
public void OnClickListener(View parentV, View v, Integer position, SNote values)
super.OnClickListener(parentV, v, position, values);
mainPresenter.showPopMenu(v, position, values);
);
recyclerAdapter.setFirstOnly(false);
recyclerAdapter.setDuration(300);
recyclerView.setAdapter(recyclerAdapter);
refreshLayout.setColorSchemeColors(getColorPrimary());
refreshLayout.setOnRefreshListener(mainPresenter);
当中,设置了recyclerView的 NotesAdapter
适配器,设置了SwipeRefreshLayout的主题颜色和刷新监听器,当然也传递给MainPresenter进行处理。
2.5.3 ProgressWheel
ProgressWheel为 materialish-progress
库中的一个进度环控件,在安卓低版本中实现MaterialDesign中自带效果,用法代码如下:
<com.pnikosis.materialishprogress.ProgressWheel
android:id="@+id/progress_wheel"
android:layout_width="75dp"
android:layout_height="75dp"
android:visibility="visible"
android:layout_gravity="center"
wheel:matProg_spinSpeed="1.2"
wheel:matProg_barColor="?attr/colorPrimary"
wheel:matProg_progressIndeterminate="true" />
2.5.4 Toolbar阴影
如何解决Toolbar在低版本安卓上效果不好,比如没有阴影效果,作者很机智地include了一个阴影效果布局:
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="6dp"
android:background="@drawable/toolbar_shadow" />
drawable/toolbar_shadow:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient
android:startColor="@android:color/transparent"
android:endColor="@color/light_grey"
android:angle="90"/>
</shape>
2.5.5 BetterFab
BetterFab也是作者重写的一个基于FloatingActionButton的自定义控件,主要增加了强制隐藏方法,该功能体现在 回收站
功能中FloatingActionButton被隐藏掉了,也得以猜测到此应用中抽屉切换并非切换Fragment而是通过隐藏和显示模块实现的。
public class BetterFab extends FloatingActionButton
private boolean forceHide = false;
...
public void setForceHide(boolean forceHide)
this.forceHide = forceHide;
if (!forceHide)
setVisibility(VISIBLE);
else
setVisibility(GONE);
//if hide,disable animation
public boolean canAnimation()
return !isForceHide();
2.5.6 抽屉中的ListView
抽屉中的ListView包含了几个不常用的属性,值得一看。
<ListView android:id="@+id/left_drawer_listview"
android:layout_width="@dimen/drawer_width"
android:layout_height="0dp"
android:layout_weight="1.0"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="?attr/colorPrimary"/>
- choiceMode: 选择模式: 多选和单选,默认不设定,此处单选便于用户知道自己所在的选项卡,如图所示:
- divider: 分隔线
- dividerHeight: 分隔线高度
分析完主布局,继续回到MainActivity。
2.6 NotesAdapter
首先回到之前提到了RecyclerView,其中的NotesAdapter是一个比较重要的东西,关乎着笔记列表的展示和操作。
2.6.1 承接关系
public class NotesAdapter extends BaseRecyclerViewAdapter<SNote> implements Filterable
...
继承自BaseRecyclerViewAdapter,而BaseRecyclerViewAdapter才继承自真正应该继承的RecyclerView.Adapter
2.6.1.1 BaseRecyclerViewAdapter
2.6.1.1.1 增删改方法
在BaseRecyclerViewAdapter中,首先是增加了对传入List的增删改方法,此处只贴上增加的方法:
public void add(E e)
this.list.add(0, e);
notifyItemInserted(0);
此处notifyItemInserted(int position)方法是用于通知RecyclerView有新的数据增加,对于不使用notifyDataSetChanged()方法,笔者猜测是为了防止刷新数据时列表跳回到表首。
2.6.1.1.2 内部点击事件
private void addInternalClickListener(final View itemV, final Integer position, final E valuesMap)
if (canClickItem != null)
for (Integer key : canClickItem.keySet())
View inView = itemV.findViewById(key);
final onInternalClickListener<E> listener = canClickItem.get(key);
if (inView != null && listener != null)
inView.setOnClickListener((view) ->
listener.OnClickListener(itemV, view, position,
valuesMap)
);
inView.setOnLongClickListener((view) ->
listener.OnLongClickListener(itemV, view, position,
valuesMap);
return true;
);
这段代码逻辑比较复杂,主要是对内部的点击事件进行回调,暂时先不作详细分析。
2.6.1.1.3 动画效果
首先animate方法用于执行getAnimators()中获得的所有动画效果:
protected void animate(RecyclerView.ViewHolder holder, int position)
if (!isFirstOnly || position > mLastPosition)
for (Animator anim : getAnimators(holder.itemView))
anim.setDuration(mDuration).start();
anim.setInterpolator(mInterpolator);
mLastPosition = position;
else
ViewHelper.clear(holder.itemView);
getAnimators()方法在子类NotesAdapter进行实现:
@Override
protected Animator[] getAnimators(View view)
if (view.getMeasuredHeight() <=0)
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.05f, 1.0f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.05f, 1.0f);
return new ObjectAnimator[]scaleX, scaleY;
return new Animator[]
ObjectAnimator.ofFloat(view, "scaleX", 1.05f, 1.0f),
ObjectAnimator.ofFloat(view, "scaleY", 1.05f, 1.0f),
;
此处用到了属性动画相关知识。
2.6.2 onCreateViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
mContext = parent.getContext();
final View view = LayoutInflater.from(mContext).inflate(R.layout.notes_item_layout, parent, false);
return new NotesItemViewHolder(view);
在创建单个Item视图的ViewHolder时,先使用LayoutInflater填充出一个view,再通过NotesItemViewHolder包装获得ViewHolder。
2.6.2.1 NotesItemViewHolder
NotesItemViewHolder继承自RecyclerView.ViewHolder,是一个为了提高性能的ViewHolder。
首先看构造函数:
private final TextView mNoteLabelTextView;
private final TextView mNoteContentTextView;
private final TextView mNoteTimeTextView;
public NotesItemViewHolder(View parent)
super(parent);
mNoteLabelTextView = (TextView) parent.findViewById(R.id.note_label_text);
mNoteContentTextView = (TextView) parent.findViewById(R.id.note_content_text);
mNoteTimeTextView = (TextView) parent.findViewById(R.id.note_last_edit_text);
这里并没有使用ButterKnife,也许是因为ButterKnife的使用有需要传入Activity参数的限制,或是因为成员变量为final类型,需要即时初始化。
类中还包含设置TextView的方法,用于设置每个Item View的文字。
2.6.3 绑定ViewHolder
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
super.onBindViewHolder(viewHolder, position);
NotesItemViewHolder holder = (NotesItemViewHolder) viewHolder;
SNote note = list.get(position);
if (note == null)
return;
String label = "";
if (mContext != null)
boolean b = TextUtils.equals(mContext.getString(R.string.default_label), note.getLabel());
label = b? "": note.getLabel();
holder.setLabelText(label);
holder.setContentText(note.getContent());
holder.setTimeText(TimeUtils.getConciseTime(note.getLastOprTime(), mContext));
animate(viewHolder, position);
此方法主要对ViewHolder中的控件进行赋值,在加载每个子项时调用此方法。
2.6.4 过滤操作
private static class NoteFilter extends Filter
private final NotesAdapter adapter;
private final List<SNote> originalList;
private final List<SNote> filteredList;
private NoteFilter(NotesAdapter adapter, List<SNote> originalList)
super();
this.adapter = adapter;
this.originalList = new LinkedList<>(originalList);
this.filteredList = new ArrayList<>();
@Override
protected FilterResults performFiltering(CharSequence constraint)
filteredList.clear();
final FilterResults results = new FilterResults();
if (constraint.length() == 0)
filteredList.addAll(originalList);
else
for ( SNote note : originalList)
if (note.getContent().contains(constraint) || note.getLabel().contains(constraint))
filteredList.add(note);
results.values = filteredList;
results.count = filteredList.size();
return results;
@Override
protected void publishResults(CharSequence constraint, FilterResults results)
adapter.list.clear();
adapter.list.addAll((ArrayList<SNote>) results.values);
adapter.notifyDataSetChanged();
此类主要由搜索功能调用,构造函数对originalList进行赋值,performFiltering(…)方法进行过滤操作,过滤后列表存入filteredList,并且返回FilterResults以便后用,publishResults(…)方法进行展示filteredList的内容。
2.7 DrawerView的初始化
DrawerView视图比较简单,只有一个ListView,不过其中包含很多细节值得学习,而且作者为了后期的可拓展性定义了抽象类和接口。
2.7.1 DrawerListAdapter
首先是DrawerListAdapter,继承自SimpleListAdapter,而SimpleListAdapter又继承自BaseListAdapter,然后才是继承自API的BaseAdapter,继承结构如图:
Android Studio中按快捷键F4查看类继承结构图
2.7.1.1 BaseListAdapter
与BaseRecyclerViewAdapter类似,同样包含需要传入参数进行初始化操作的列表,以及增删改方法,以及回调的点击事件接口。
2.7.1.2 SimpleListAdapter
@Override
public View bindView(int position, View convertView, ViewGroup parent)
Holder holder;
if (convertView == null)
convertView = LayoutInflater.from(mContext).inflate(getLayout(), null);
holder = new Holder();
holder.textView = (TextView)convertView.findViewById(R.id.textView);
convertView.setTag(holder);
else
holder = (Holder)convertView.getTag();
holder.textView.setText(list.get(position));
return convertView;
SimpleListAdapter中,实现了抽象方法bindView(…),并且使用了ListView的缓存机制,但bindView(…)中填充Item视图并没有写死,而是交给了子类DrawerListAdapter去进行实现。
2.7.1.3 DrawerListAdapter
@Override
protected int getLayout() return R.layout.drawer_list_item_layout;
2.7.1.3.1 布局
布局仅用了一个简洁的TextView,但TextView中包含了几个不常见的属性:
android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="Medium Text"
android:singleLine="true"
- android:textAppearance: 系统文字外观,’?’代表试探系统是否有此外观,没有则使用默认外观
- tools:text: 告诉Android Studio在运行时忽略该属性,只在设计布局时有效
- android:singleLine: 就是单行显示文字
2.7.2 抽屉开关按钮
通过 mDrawerLayout.setDrawerListener(mDrawerToggle);
为抽屉加上开关抽屉的监听。对监听器的配置如下:
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, 0, 0)
@Override
public void onDrawerOpened(View drawerView)
super.onDrawerOpened(drawerView);
invalidateOptionsMenu();
mainPresenter.onDrawerOpened();
@Override
public void onDrawerClosed(View drawerView)
super.onDrawerClosed(drawerView);
invalidateOptionsMenu();
mainPresenter.onDrawerClosed();
;
mDrawerToggle.setDrawerIndicatorEnabled(true); // 指示器: 用于动画展示开关操作按钮变化
2.7.3 设置抽屉遮帘颜色
mDrawerLayout.setScrimColor(getCompactColor(R.color.drawer_scrim_color));
此处放上设置遮帘为蓝色后的效果图:
2.8 PopupMenu
在每个CardView上面需要显示菜单,包含”编辑”和”回收”,显示PopupMenu方法如下:
@Override
public void showNormalPopupMenu(View view, SNote note)
PopupMenu popup = new PopupMenu(this, view);
popup.getMenuInflater()
.inflate(R.menu.menu_notes_more, popup.getMenu());
popup.setOnMenuItemClickListener((item -> mainPresenter.onPopupMenuClick(item.getItemId(), note)));
popup.show();
2.9 ActionBar上的搜索框
2.9.1 定义菜单
首先在menu.xml中新增搜索项:
<item
android:id="@+id/action_search"
android:icon="@drawable/abc_ic_search_api_mtrl_alpha"
android:title="@string/search"
app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="android.support.v7.widget.SearchView">
</item>
2.9.2 初始化SearchView
@Override
public boolean onCreateOptionsMenu(Menu menu)
getMenuInflater().inflate(R.menu.menu_main, menu);
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
MenuItem searchItem = menu.findItem(R.id.action_search);
//searchItem.expandActionView(); // 默认展开搜索框
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
ComponentName componentName = getComponentName();
searchView.setSearchableInfo(
searchManager.getSearchableInfo(componentName));
searchView.setQueryHint(getString(R.string.search_note));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener()
@Override
public boolean onQueryTextSubmit(String s)
return true;
@Override
public boolean onQueryTextChange(String s)
recyclerAdapter.getFilter().filter(s); // 文字改变就即时处理搜索
return true;
);
MenuItemCompat.setOnActionExpandListener(searchItem, mainPresenter); // 监听搜索框是否打开,用于隐藏FloatingActionBar和禁用下拉刷新
return true;
2.10 处理菜单事件
@Override
public boolean onOptionsItemSelected(MenuItem item)
if(mDrawerToggle.onOptionsItemSele以上是关于《极简笔记》源码分析的主要内容,如果未能解决你的问题,请参考以下文章