Merge pull request '1.19' (#11) from jiangtianxiang_branch into master

pull/19/head
p7tupf26b 1 month ago
commit 1d2fbbff5e

@ -29,7 +29,7 @@ import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.ui.NotesRecyclerViewAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;

@ -0,0 +1,68 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.graphics.Rect;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
/**
*
* <p>
* RecyclerView
*
* </p>
*/
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
private boolean includeEdge;
/**
*
* @param spanCount
* @param spacing
* @param includeEdge
*/
public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
this.spanCount = spanCount;
this.spacing = spacing;
this.includeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int column = position % spanCount;
if (includeEdge) {
outRect.left = spacing - column * spacing / spanCount;
outRect.right = (column + 1) * spacing / spanCount;
if (position < spanCount) {
outRect.top = spacing;
}
outRect.bottom = spacing;
} else {
outRect.left = column * spacing / spanCount;
outRect.right = spacing - (column + 1) * spacing / spanCount;
if (position >= spanCount) {
outRect.top = spacing;
}
}
}
}

@ -0,0 +1,294 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
/**
*
* <p>
* RecyclerView
* 线
*
* </p>
*/
public class LayoutManagerController {
private static final String TAG = "LayoutManagerController";
private static final String PREF_LAYOUT_TYPE = "layout_type";
private static final String PREF_GRID_SPAN_COUNT = "grid_span_count";
private static final String PREF_ITEM_SPACING = "item_spacing";
private Context mContext;
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mCurrentLayoutManager;
private LayoutType mCurrentLayoutType;
private SharedPreferences mPreferences;
private LayoutChangeListener mListener;
/**
*
*/
public interface LayoutChangeListener {
void onLayoutChanged(LayoutType newLayoutType);
void onLayoutChangeFailed(Exception e);
}
/**
*
* @param context
* @param recyclerView RecyclerView
* @param listener
*/
public LayoutManagerController(Context context, RecyclerView recyclerView, LayoutChangeListener listener) {
mContext = context;
mRecyclerView = recyclerView;
mListener = listener;
mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
// 加载保存的布局类型
String savedLayoutKey = mPreferences.getString(PREF_LAYOUT_TYPE, LayoutType.LINEAR.getKey());
mCurrentLayoutType = LayoutType.fromKey(savedLayoutKey);
Log.d(TAG, "LayoutManagerController initialized with layout: " + mCurrentLayoutType.getDisplayName());
}
/**
*
*/
public void initializeLayout() {
switchLayout(mCurrentLayoutType, false);
}
/**
*
* @param layoutType
* @param animate
* @return
*/
public boolean switchLayout(LayoutType layoutType, boolean animate) {
if (layoutType == mCurrentLayoutType) {
Log.d(TAG, "Already in " + layoutType.getDisplayName() + " mode");
return true;
}
long startTime = System.currentTimeMillis();
try {
// 保存滚动位置
int scrollPosition = 0;
if (mRecyclerView.getLayoutManager() != null) {
if (mRecyclerView.getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
scrollPosition = layoutManager.findFirstVisibleItemPosition();
} else if (mRecyclerView.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
scrollPosition = layoutManager.findFirstVisibleItemPosition();
} else if (mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) mRecyclerView.getLayoutManager();
int[] positions = layoutManager.findFirstVisibleItemPositions(null);
scrollPosition = positions.length > 0 ? positions[0] : 0;
}
}
// 移除所有装饰
int decorationCount = mRecyclerView.getItemDecorationCount();
for (int i = decorationCount - 1; i >= 0; i--) {
mRecyclerView.removeItemDecorationAt(i);
}
// 创建新的布局管理器
RecyclerView.LayoutManager newLayoutManager = createLayoutManager(layoutType);
// 应用新布局
if (animate) {
mRecyclerView.setLayoutManager(newLayoutManager);
} else {
mRecyclerView.setLayoutManager(newLayoutManager);
}
// 添加间距装饰
addItemDecoration(layoutType);
// 保存布局偏好
saveLayoutPreference(layoutType);
// 恢复滚动位置
restoreScrollPosition(scrollPosition);
mCurrentLayoutManager = newLayoutManager;
mCurrentLayoutType = layoutType;
long duration = System.currentTimeMillis() - startTime;
Log.d(TAG, "Layout switched to " + layoutType.getDisplayName() + " in " + duration + "ms");
// 通知监听器
if (mListener != null) {
mListener.onLayoutChanged(layoutType);
}
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to switch layout: " + e.getMessage(), e);
// 通知监听器失败
if (mListener != null) {
mListener.onLayoutChangeFailed(e);
}
return false;
}
}
/**
*
* @param layoutType
* @return
*/
private RecyclerView.LayoutManager createLayoutManager(LayoutType layoutType) {
int spanCount = getGridSpanCount();
switch (layoutType) {
case LINEAR:
return new LinearLayoutManager(mContext);
case GRID:
return new GridLayoutManager(mContext, spanCount);
case STAGGERED:
return new StaggeredGridLayoutManager(spanCount, StaggeredGridLayoutManager.VERTICAL);
default:
return new LinearLayoutManager(mContext);
}
}
/**
*
* @param layoutType
*/
private void addItemDecoration(LayoutType layoutType) {
int spacing = getItemSpacing();
switch (layoutType) {
case LINEAR:
mRecyclerView.addItemDecoration(new NoteItemDecoration(mContext));
break;
case GRID:
mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(getGridSpanCount(), spacing, true));
break;
case STAGGERED:
mRecyclerView.addItemDecoration(new StaggeredGridSpacingItemDecoration(getGridSpanCount(), spacing, true));
break;
}
}
/**
*
* @param position
*/
private void restoreScrollPosition(int position) {
if (mRecyclerView.getAdapter() != null && position >= 0 && position < mRecyclerView.getAdapter().getItemCount()) {
mRecyclerView.scrollToPosition(position);
}
}
/**
*
* @param layoutType
*/
private void saveLayoutPreference(LayoutType layoutType) {
mPreferences.edit()
.putString(PREF_LAYOUT_TYPE, layoutType.getKey())
.apply();
}
/**
*
* @return
*/
public int getGridSpanCount() {
return mPreferences.getInt(PREF_GRID_SPAN_COUNT, 2);
}
/**
*
* @param spanCount
*/
public void setGridSpanCount(int spanCount) {
if (spanCount < 1) spanCount = 1;
if (spanCount > 4) spanCount = 4;
mPreferences.edit()
.putInt(PREF_GRID_SPAN_COUNT, spanCount)
.apply();
// 重新应用布局
switchLayout(mCurrentLayoutType, true);
}
/**
*
* @return
*/
public int getItemSpacing() {
return mPreferences.getInt(PREF_ITEM_SPACING, 16);
}
/**
*
* @param spacing
*/
public void setItemSpacing(int spacing) {
if (spacing < 0) spacing = 0;
if (spacing > 48) spacing = 48;
mPreferences.edit()
.putInt(PREF_ITEM_SPACING, spacing)
.apply();
// 重新应用布局
switchLayout(mCurrentLayoutType, true);
}
/**
*
* @return
*/
public LayoutType getCurrentLayoutType() {
return mCurrentLayoutType;
}
/**
*
* @return
*/
public LayoutType getNextLayoutType() {
LayoutType[] types = LayoutType.values();
int currentIndex = 0;
for (int i = 0; i < types.length; i++) {
if (types[i] == mCurrentLayoutType) {
currentIndex = i;
break;
}
}
return types[(currentIndex + 1) % types.length];
}
}

@ -0,0 +1,199 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import net.micode.notes.R;
/**
*
* <p>
*
*
* </p>
*/
public class LayoutSettingsDialog {
private Context mContext;
private LayoutManagerController mLayoutManagerController;
private AlertDialog mDialog;
private Spinner mLayoutTypeSpinner;
private SeekBar mGridColumnsSeekBar;
private SeekBar mItemSpacingSeekBar;
private TextView mGridColumnsValue;
private TextView mItemSpacingValue;
/**
*
* @param context
* @param layoutManagerController
*/
public LayoutSettingsDialog(Context context, LayoutManagerController layoutManagerController) {
mContext = context;
mLayoutManagerController = layoutManagerController;
}
/**
*
*/
public void show() {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(R.string.layout_settings_title);
View dialogView = LayoutInflater.from(mContext).inflate(R.layout.layout_settings_dialog, null);
builder.setView(dialogView);
initViews(dialogView);
setupListeners();
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
saveSettings();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
mDialog = builder.create();
mDialog.show();
}
/**
*
* @param dialogView
*/
private void initViews(View dialogView) {
mLayoutTypeSpinner = (Spinner) dialogView.findViewById(R.id.layout_type_spinner);
mGridColumnsSeekBar = (SeekBar) dialogView.findViewById(R.id.grid_columns_seekbar);
mItemSpacingSeekBar = (SeekBar) dialogView.findViewById(R.id.item_spacing_seekbar);
mGridColumnsValue = (TextView) dialogView.findViewById(R.id.grid_columns_value);
mItemSpacingValue = (TextView) dialogView.findViewById(R.id.item_spacing_value);
// 设置布局类型选项
ArrayAdapter<LayoutType> layoutAdapter = new ArrayAdapter<>(
mContext, android.R.layout.simple_spinner_item, LayoutType.values());
mLayoutTypeSpinner.setAdapter(layoutAdapter);
// 设置当前值
LayoutType currentLayout = mLayoutManagerController.getCurrentLayoutType();
mLayoutTypeSpinner.setSelection(currentLayout.ordinal());
int gridColumns = mLayoutManagerController.getGridSpanCount();
mGridColumnsSeekBar.setProgress(gridColumns - 1);
mGridColumnsValue.setText(String.valueOf(gridColumns));
int itemSpacing = mLayoutManagerController.getItemSpacing();
mItemSpacingSeekBar.setProgress(itemSpacing / 2);
mItemSpacingValue.setText(String.valueOf(itemSpacing));
// 根据布局类型启用/禁用控件
updateControlStates(currentLayout);
}
/**
*
*/
private void setupListeners() {
mLayoutTypeSpinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
LayoutType selectedLayout = (LayoutType) parent.getItemAtPosition(position);
updateControlStates(selectedLayout);
}
@Override
public void onNothingSelected(android.widget.AdapterView<?> parent) {
}
});
mGridColumnsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int columns = progress + 1;
mGridColumnsValue.setText(String.valueOf(columns));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
mItemSpacingSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int spacing = progress * 2;
mItemSpacingValue.setText(String.valueOf(spacing));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
/**
*
* @param layoutType
*/
private void updateControlStates(LayoutType layoutType) {
switch (layoutType) {
case LINEAR:
mGridColumnsSeekBar.setEnabled(false);
mItemSpacingSeekBar.setEnabled(false);
break;
case GRID:
case STAGGERED:
mGridColumnsSeekBar.setEnabled(true);
mItemSpacingSeekBar.setEnabled(true);
break;
}
}
/**
*
*/
private void saveSettings() {
LayoutType selectedLayout = (LayoutType) mLayoutTypeSpinner.getSelectedItem();
int gridColumns = mGridColumnsSeekBar.getProgress() + 1;
int itemSpacing = mItemSpacingSeekBar.getProgress() * 2;
mLayoutManagerController.setGridSpanCount(gridColumns);
mLayoutManagerController.setItemSpacing(itemSpacing);
if (selectedLayout != mLayoutManagerController.getCurrentLayoutType()) {
mLayoutManagerController.switchLayout(selectedLayout, true);
}
mDialog.dismiss();
}
}

@ -0,0 +1,61 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
/**
*
* <p>
* 线
*
* </p>
*/
public enum LayoutType {
LINEAR("linear", "列表布局", "传统的垂直列表布局,适合大量笔记浏览"),
GRID("grid", "网格布局", "网格排列布局,适合快速浏览和预览"),
STAGGERED("staggered", "瀑布流布局", "错落有致的布局,适合不同长度的笔记展示");
private final String key;
private final String displayName;
private final String description;
LayoutType(String key, String displayName, String description) {
this.key = key;
this.displayName = displayName;
this.description = description;
}
public String getKey() {
return key;
}
public String getDisplayName() {
return displayName;
}
public String getDescription() {
return description;
}
public static LayoutType fromKey(String key) {
for (LayoutType type : values()) {
if (type.key.equals(key)) {
return type;
}
}
return LINEAR;
}
}

@ -0,0 +1,73 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
import net.micode.notes.R;
/**
*
* <p>
* RecyclerView线ListViewdivider
* </p>
*/
public class NoteItemDecoration extends ItemDecoration {
private Drawable mDivider;
private int mDividerHeight;
/**
*
* @param context
*/
public NoteItemDecoration(Context context) {
mDivider = context.getResources().getDrawable(R.drawable.list_divider);
if (mDivider != null) {
mDividerHeight = 1;
} else {
mDividerHeight = mDivider.getIntrinsicHeight();
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDividerHeight;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.bottom = mDividerHeight;
}
}

@ -0,0 +1,54 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import net.micode.notes.R;
/**
* ViewHolder
* <p>
* RecyclerViewViewHolder
* findViewById
* </p>
*/
public class NoteViewHolder extends RecyclerView.ViewHolder {
public ImageView mAlert;
public TextView mTitle;
public TextView mTime;
public TextView mCallName;
public CheckBox mCheckBox;
/**
*
* @param itemView
*/
public NoteViewHolder(View itemView) {
super(itemView);
// 查找所有子视图(只执行一次)
mAlert = (ImageView) itemView.findViewById(R.id.iv_alert_icon);
mTitle = (TextView) itemView.findViewById(R.id.tv_title);
mTime = (TextView) itemView.findViewById(R.id.tv_time);
mCallName = (TextView) itemView.findViewById(R.id.tv_name);
mCheckBox = (CheckBox) itemView.findViewById(android.R.id.checkbox);
}
}

@ -55,11 +55,14 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.DefaultItemAnimator;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
@ -68,7 +71,7 @@ import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.ui.NotesRecyclerViewAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
@ -96,7 +99,7 @@ import java.util.HashSet;
* @see NotesListAdapter
* @see GTaskSyncService
*/
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
public class NotesListActivity extends Activity implements OnClickListener, NotesRecyclerViewAdapter.OnItemLongClickListener {
// 笔记列表查询令牌
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
@ -133,10 +136,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
private BackgroundQueryHandler mBackgroundQueryHandler;
// 笔记列表适配器
private NotesListAdapter mNotesListAdapter;
private NotesRecyclerViewAdapter mNotesListAdapter;
// 笔记列表视图
private ListView mNotesListView;
private RecyclerView mNotesRecyclerView;
// 布局管理器
private LinearLayoutManager mLayoutManager;
// 布局切换控制器
private LayoutManagerController mLayoutManagerController;
// 新建笔记按钮
private Button mAddNewNote;
@ -216,7 +225,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
mNotesListAdapter.changeCursor(null);
mNotesListAdapter.swapCursor(null);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
@ -293,13 +302,43 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mContentResolver = this.getContentResolver();
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mNotesListView = (ListView) findViewById(R.id.notes_list);
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
null, false);
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
mNotesListView.setOnItemLongClickListener(this);
mNotesListAdapter = new NotesListAdapter(this);
mNotesListView.setAdapter(mNotesListAdapter);
mNotesRecyclerView = (RecyclerView) findViewById(R.id.notes_list);
mLayoutManager = new LinearLayoutManager(this);
mNotesRecyclerView.setLayoutManager(mLayoutManager);
// 创建适配器
mNotesListAdapter = new NotesRecyclerViewAdapter(this);
mNotesRecyclerView.setAdapter(mNotesListAdapter);
// 设置动画
DefaultItemAnimator animator = new DefaultItemAnimator();
animator.setAddDuration(300);
animator.setRemoveDuration(300);
animator.setMoveDuration(300);
animator.setChangeDuration(300);
mNotesRecyclerView.setItemAnimator(animator);
// 初始化布局切换控制器
mLayoutManagerController = new LayoutManagerController(this, mNotesRecyclerView,
new LayoutManagerController.LayoutChangeListener() {
@Override
public void onLayoutChanged(LayoutType newLayoutType) {
showToast("已切换到" + newLayoutType.getDisplayName());
}
@Override
public void onLayoutChangeFailed(Exception e) {
showToast("布局切换失败: " + e.getMessage());
}
});
// 应用保存的布局
mLayoutManagerController.initializeLayout();
// 设置点击和长按监听
mNotesListAdapter.setOnItemClickListener(new OnListItemClickListener());
mNotesListAdapter.setOnItemLongClickListener(this);
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
mAddNewNote.setOnClickListener(this);
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
@ -314,9 +353,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
/**
*
*
* ListView.MultiChoiceModeListener
* ActionMode.Callback
*/
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private class ModeCallback implements ActionMode.Callback, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu;
private ActionMode mActionMode;
private MenuItem mMoveMenu;
@ -341,7 +380,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
mActionMode = mode;
mNotesListAdapter.setChoiceMode(true);
mNotesListView.setLongClickable(false);
mAddNewNote.setVisibility(View.GONE);
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
@ -422,7 +460,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
*/
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
mAddNewNote.setVisibility(View.VISIBLE);
}
@ -510,15 +547,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
* also change. This is very bad, just for the UI designer's strong requirement.
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
View view = mNotesRecyclerView.getChildAt(mNotesRecyclerView.getChildCount() - 1);
if (view != null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY();
mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
mDispatch = true;
return mNotesListView.dispatchTouchEvent(event);
return mNotesRecyclerView.dispatchTouchEvent(event);
}
}
break;
@ -527,7 +563,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
if (mDispatch) {
mDispatchY += (int) event.getY() - mOriginY;
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event);
return mNotesRecyclerView.dispatchTouchEvent(event);
}
break;
}
@ -535,7 +571,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event);
return mNotesRecyclerView.dispatchTouchEvent(event);
}
break;
}
@ -594,7 +630,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
mNotesListAdapter.changeCursor(cursor);
mNotesListAdapter.swapCursor(cursor);
break;
case FOLDER_LIST_QUERY_TOKEN:
if (cursor != null && cursor.getCount() > 0) {
@ -979,9 +1015,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
@Override
public void onContextMenuClosed(Menu menu) {
if (mNotesListView != null) {
mNotesListView.setOnCreateContextMenuListener(null);
}
super.onContextMenuClosed(menu);
}
@ -1100,6 +1133,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
case R.id.menu_search:
onSearchRequested();
break;
case R.id.menu_switch_layout:
showLayoutSettingsDialog();
break;
default:
break;
}
@ -1168,6 +1204,28 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}.execute();
}
/**
*
* <p>
*
* </p>
*/
private void showLayoutSettingsDialog() {
LayoutSettingsDialog dialog = new LayoutSettingsDialog(this, mLayoutManagerController);
dialog.show();
}
/**
* Toast
* <p>
*
* </p>
* @param message
*/
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
/**
*
* <p>
@ -1202,22 +1260,21 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
* </ul>
* </p>
*/
private class OnListItemClickListener implements OnItemClickListener {
private class OnListItemClickListener implements NotesRecyclerViewAdapter.OnItemClickListener {
/**
*
*
* @param parent
* @param view
* @param position
* @param id ID
*/
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
public void onItemClick(View view, int position, long id) {
Cursor cursor = (Cursor) mNotesListAdapter.getItem(position);
if (cursor != null) {
NoteItemData item = new NoteItemData(NotesListActivity.this, cursor);
if (mNotesListAdapter.isInChoiceMode()) {
if (item.getType() == Notes.TYPE_NOTE) {
position = position - mNotesListView.getHeaderViewsCount();
mModeCallBack.onItemCheckedStateChanged(null, position, id,
!mNotesListAdapter.isSelectedItem(position));
}
@ -1279,24 +1336,25 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
/**
*
*
* @param parent
* @param view
* @param position
* @param id ID
* @return true
*/
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
public boolean onItemLongClick(View view, int position, long id) {
Cursor cursor = (Cursor) mNotesListAdapter.getItem(position);
if (cursor != null) {
mFocusNoteDataItem = new NoteItemData(NotesListActivity.this, cursor);
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
if (mNotesListView.startActionMode(mModeCallBack) != null) {
if (startActionMode(mModeCallBack) != null) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
Log.e(TAG, "startActionMode fails");
}
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
registerForContextMenu(view);
openContextMenu(view);
}
}
return false;

@ -0,0 +1,465 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import androidx.recyclerview.widget.RecyclerView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
import java.util.HashSet;
/**
* RecyclerView
*
* RecyclerView.AdapterRecyclerView
*
* NotesListAdapter使RecyclerView
*
*
* 1. NoteViewHolder
* 2.
* 3. ID
* 4.
* 5.
*
* @see NoteViewHolder
* @see NoteItemData
*/
public class NotesRecyclerViewAdapter extends RecyclerView.Adapter<NoteViewHolder> {
private static final String TAG = "NotesRecyclerViewAdapter";
private Context mContext;
private Cursor mCursor;
private SparseArray<Boolean> mSelectedIndex;
private int mNotesCount;
private boolean mChoiceMode;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
/**
*
*
* ID
*/
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
}
/**
*
*/
public interface OnItemClickListener {
void onItemClick(View view, int position, long id);
}
/**
*
*/
public interface OnItemLongClickListener {
boolean onItemLongClick(View view, int position, long id);
}
/**
*
*
* Map
*
* @param context null
*/
public NotesRecyclerViewAdapter(Context context) {
mSelectedIndex = new SparseArray<>();
mContext = context;
mNotesCount = 0;
}
/**
* ViewHolder
*
* @param parent
* @param viewType
* @return NoteViewHolder
*/
@Override
public NoteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext)
.inflate(R.layout.note_item, parent, false);
final NoteViewHolder holder = new NoteViewHolder(view);
// 设置点击监听
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
long id = getItemId(position);
mOnItemClickListener.onItemClick(v, position, id);
}
}
}
});
// 设置长按监听
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
long id = getItemId(position);
return mOnItemLongClickListener.onItemLongClick(v, position, id);
}
}
return false;
}
});
return holder;
}
/**
* ViewHolder
*
* ViewHolder
*
* @param holder ViewHolder
* @param position
*/
@Override
public void onBindViewHolder(NoteViewHolder holder, int position) {
if (mCursor != null && mCursor.moveToPosition(position)) {
NoteItemData itemData = new NoteItemData(mContext, mCursor);
// 绑定数据到视图
bindViewHolder(holder, itemData, position);
}
}
/**
* ViewHolder
*
* @param holder ViewHolder
* @param data
* @param position
*/
private void bindViewHolder(NoteViewHolder holder, NoteItemData data, int position) {
// 处理复选框
if (mChoiceMode && data.getType() == Notes.TYPE_NOTE) {
holder.mCheckBox.setVisibility(View.VISIBLE);
holder.mCheckBox.setChecked(isSelectedItem(position));
} else {
holder.mCheckBox.setVisibility(View.GONE);
}
// 处理提醒图标
if (data.hasAlert()) {
holder.mAlert.setImageResource(R.drawable.clock);
holder.mAlert.setVisibility(View.VISIBLE);
} else {
holder.mAlert.setVisibility(View.GONE);
}
// 处理标题
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
holder.mCallName.setVisibility(View.GONE);
holder.mAlert.setVisibility(View.VISIBLE);
holder.mTitle.setTextAppearance(mContext, R.style.TextAppearancePrimaryItem);
holder.mTitle.setText(mContext.getString(R.string.call_record_folder_name)
+ mContext.getString(R.string.format_folder_files_count, data.getNotesCount()));
holder.mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
holder.mCallName.setVisibility(View.VISIBLE);
holder.mCallName.setText(data.getCallName());
holder.mTitle.setTextAppearance(mContext, R.style.TextAppearanceSecondaryItem);
holder.mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
} else {
holder.mCallName.setVisibility(View.GONE);
holder.mTitle.setTextAppearance(mContext, R.style.TextAppearancePrimaryItem);
if (data.getType() == Notes.TYPE_FOLDER) {
holder.mTitle.setText(data.getSnippet()
+ mContext.getString(R.string.format_folder_files_count,
data.getNotesCount()));
holder.mAlert.setVisibility(View.GONE);
} else {
holder.mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
holder.mAlert.setImageResource(R.drawable.clock);
holder.mAlert.setVisibility(View.VISIBLE);
} else {
holder.mAlert.setVisibility(View.GONE);
}
}
}
// 设置时间
holder.mTime.setText(android.text.format.DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 设置背景
setBackground(holder.itemView, data);
}
/**
*
* @param view
* @param data
*/
private void setBackground(View view, NoteItemData data) {
int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) {
int bgRes;
if (data.isSingle() || data.isOneFollowingFolder()) {
bgRes = NoteItemBgResources.getNoteBgSingleRes(id);
} else if (data.isLast()) {
bgRes = NoteItemBgResources.getNoteBgLastRes(id);
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
bgRes = NoteItemBgResources.getNoteBgFirstRes(id);
} else {
bgRes = NoteItemBgResources.getNoteBgNormalRes(id);
}
view.setBackgroundResource(bgRes);
} else {
view.setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
/**
*
*
* @return null0
*/
@Override
public int getItemCount() {
return mCursor != null ? mCursor.getCount() : 0;
}
/**
* ID
*
* @param position
* @return IDnull0
*/
public long getItemId(int position) {
if (mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getLong(mCursor.getColumnIndexOrThrow(Notes.NoteColumns.ID));
}
return 0;
}
/**
*
*
* @param position
* @return nullnull
*/
public Object getItem(int position) {
if (mCursor != null && mCursor.moveToPosition(position)) {
return mCursor;
}
return null;
}
/**
*
*
* @param position 0
* @param checked
*/
public void setCheckedItem(int position, boolean checked) {
mSelectedIndex.put(position, checked);
notifyItemChanged(position);
}
/**
*
*
* @return truefalse
*/
public boolean isInChoiceMode() {
return mChoiceMode;
}
/**
*
*
* @param mode truefalse退
*/
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
notifyDataSetChanged();
}
/**
*
*
* @param checked truefalse
*/
public void selectAll(boolean checked) {
if (mCursor != null) {
for (int i = 0; i < getItemCount(); i++) {
if (mCursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(mCursor) == Notes.TYPE_NOTE) {
mSelectedIndex.put(i, checked);
}
}
}
notifyDataSetChanged();
}
}
/**
* ID
*
* @return IDHashSet
*/
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<>();
for (int i = 0; i < mSelectedIndex.size(); i++) {
int key = mSelectedIndex.keyAt(i);
if (mSelectedIndex.get(key)) {
long id = getItemId(key);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
/**
*
*
* @return HashSetnull
*/
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<>();
for (int i = 0; i < mSelectedIndex.size(); i++) {
int key = mSelectedIndex.keyAt(i);
if (mSelectedIndex.get(key)) {
Cursor c = (Cursor) getItem(key);
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
/**
*
*
* @return 0
*/
public int getSelectedCount() {
int count = 0;
for (int i = 0; i < mSelectedIndex.size(); i++) {
if (mSelectedIndex.get(mSelectedIndex.keyAt(i))) {
count++;
}
}
return count;
}
/**
*
*
* @return truefalse
*/
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
/**
*
*
* @param position 0
* @return truefalse
*/
public boolean isSelectedItem(int position) {
return mSelectedIndex.get(position, false);
}
/**
*
*
* @param newCursor
*/
public Cursor swapCursor(Cursor newCursor) {
Cursor oldCursor = mCursor;
mCursor = newCursor;
calcNotesCount();
notifyDataSetChanged();
return oldCursor;
}
/**
*
*/
private void calcNotesCount() {
mNotesCount = 0;
if (mCursor != null) {
for (int i = 0; i < getItemCount(); i++) {
if (mCursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(mCursor) == Notes.TYPE_NOTE) {
mNotesCount++;
}
}
}
}
}
/**
*
*
* @param listener
*/
public void setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
}
/**
*
*
* @param listener
*/
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
}
}

@ -0,0 +1,70 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.graphics.Rect;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
/**
*
* <p>
* RecyclerView
*
* </p>
*/
public class StaggeredGridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
private boolean includeEdge;
/**
*
* @param spanCount
* @param spacing
* @param includeEdge
*/
public StaggeredGridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
this.spanCount = spanCount;
this.spacing = spacing;
this.includeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
StaggeredGridLayoutManager.LayoutParams params =
(StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
int spanIndex = params.getSpanIndex();
if (includeEdge) {
outRect.left = spacing - spanIndex * spacing / spanCount;
outRect.right = (spanIndex + 1) * spacing / spanCount;
if (params.getViewAdapterPosition() < spanCount) {
outRect.top = spacing;
}
outRect.bottom = spacing;
} else {
outRect.left = spanIndex * spacing / spanCount;
outRect.right = spacing - (spanIndex + 1) * spacing / spanCount;
if (params.getViewAdapterPosition() >= spanCount) {
outRect.top = spacing;
}
}
}
}

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:height="1dp" />
<solid android:color="#E0E0E0" />
</shape>

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/layout_linear"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<Spinner
android:id="@+id/layout_type_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/layout_grid_columns"
android:textSize="14sp"
android:layout_marginEnd="16dp" />
<TextView
android:id="@+id/grid_columns_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2"
android:textSize="18sp"
android:textStyle="bold"
android:minWidth="30dp"
android:gravity="center" />
<SeekBar
android:id="@+id/grid_columns_seekbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="3"
android:progress="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/layout_item_spacing"
android:textSize="14sp"
android:layout_marginEnd="16dp" />
<TextView
android:id="@+id/item_spacing_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="16"
android:textSize="18sp"
android:textStyle="bold"
android:minWidth="30dp"
android:gravity="center" />
<SeekBar
android:id="@+id/item_spacing_seekbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="24"
android:progress="8" />
</LinearLayout>
</LinearLayout>

@ -39,15 +39,14 @@
android:textColor="#FFEAD1AE"
android:textSize="@dimen/text_font_size_medium" />
<ListView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/notes_list"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="@null" />
android:overScrollMode="never"
android:scrollbars="vertical" />
</LinearLayout>
<Button

@ -36,4 +36,8 @@
<item
android:id="@+id/menu_search"
android:title="@string/menu_search"/>
<item
android:id="@+id/menu_switch_layout"
android:title="@string/menu_switch_layout"/>
</menu>

@ -48,6 +48,15 @@
<string name="menu_search">Search</string>
<string name="menu_delete">Delete</string>
<string name="menu_move">Move to folder</string>
<string name="menu_switch_layout">Switch Layout</string>
<string name="layout_linear">List Layout</string>
<string name="layout_grid">Grid Layout</string>
<string name="layout_staggered">Staggered Layout</string>
<string name="layout_switch_success">Layout switched to %s</string>
<string name="layout_switch_failed">Failed to switch layout: %s</string>
<string name="layout_settings_title">Layout Settings</string>
<string name="layout_grid_columns">Grid Columns</string>
<string name="layout_item_spacing">Item Spacing</string>
<string name="menu_select_title">%d selected</string>
<string name="menu_select_none">Nothing selected, the operation is invalid</string>
<string name="menu_select_all">Select all</string>

Loading…
Cancel
Save