【Android】SDK NoteEditor源码研究(一、组件重载)
1 package com.jercy.android.SDKNotePad;
2
3 import android.app.Activity;
4 import android.content.ComponentName;
5 import android.content.ContentValues;
6 import android.content.Context;
7 import android.content.Intent;
8 import android.database.Cursor;
9 import android.graphics.Canvas;
10 import android.graphics.Paint;
11 import android.graphics.Rect;
12 import android.net.Uri;
13 import android.os.Bundle;
14 import android.util.AttributeSet;
15 import android.util.Log;
16 import android.view.Menu;
17 import android.view.MenuItem;
18 import android.widget.EditText;
19
20 import com.jercy.android.SDKNotePad.NotePad.Notes;
21
22 public class NoteEditor extends Activity {
23
24 private static final String TAG = "NoteEditor";
25
26 private static final String[] PROJECTION = new String[]{
27 Notes._ID, // 0
28 Notes.NOTE, // 1
29 };
30 private static final int COLUMN_INDEX_NOTE = 1;
31
32 //用来保存便签信息,以便在退出或者失去控制时能够及时更新到数据库中
33 private static final String ORIGINAL_CONTENT = "origContent";
34
35 private static final int REVERT_ID = Menu.FIRST;
36 private static final int DISCARD_ID = Menu.FIRST + 1;
37 private static final int DELETE_ID = Menu.FIRST + 2;
38
39 //用来区分两种操作状态
40 private static final int STATE_EDIT = 0;
41 private static final int STATE_INSERT = 1;
42
43 private int mState;
44 private boolean mNoteOnly = false;
45 private Uri mUri;
46 private Cursor mCursor;
47 private EditText mText;
48 private String mOriginalContent;
49
50 /**
51 * 一个定制的EditText每行信息都会用下滑线显示.
52 */
53 public static class LinedEditText extends EditText{
54 private Rect mRect;
55 private Paint mPaint;
56 public LinedEditText(Context context, AttributeSet attrs) {
57 super(context, attrs);
58
59 mRect = new Rect();
60 mPaint = new Paint();
61 mPaint.setStyle(Paint.Style.STROKE);
62 mPaint.setColor(0x800000FF);
63 }
64 @Override
65 protected void onDraw(Canvas canvas) {
66 int count = getLineCount();
67 Rect r = mRect;
68 Paint paint = mPaint;
69
70 for (int i = 0; i < count; i++) {
71 int baseline = getLineBounds(i, r);
72
73 canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
74 }
75 super.onDraw(canvas);
76 }
77 }
78
79 @Override
80 protected void onCreate(Bundle savedInstanceState) {
81 super.onCreate(savedInstanceState);
82 Log.i(TAG, "Enter in NoteEditor的onCreate方法");
83 final Intent intent = getIntent();
84
85 // Do some setup based on the action being performed.
86 final String action = intent.getAction();
87 if (Intent.ACTION_EDIT.equals(action)) {
88 // Requested to edit: set that state, and the data being edited.
89 mState = STATE_EDIT;
90 mUri = intent.getData();
91 } else if (Intent.ACTION_INSERT.equals(action)) {
92 // Requested to insert: set that state, and create a new entry
93 // in the container.
94 mState = STATE_INSERT;
95 Log.i(TAG, "getContentResolver().insert(intent.getData(), null),其中intent.getData().toString()为:"+intent.getData().toString());
96 mUri = getContentResolver().insert(intent.getData(), null);
97
98 // If we were unable to create a new note, then just finish
99 // this activity. A RESULT_CANCELED will be sent back to the
100 // original activity if they requested a result.
101 if (mUri == null) {
102 Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
103 finish();
104 return;
105 }
106
107 // The new entry was created, so assume all will end well and
108 // set the result to be returned.
109 Log.i(TAG, "返回RESULT_OK,setAction(mUri.toString(),其中mUri.toString()为:"+mUri.toString());
110 setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
111
112 } else {
113 // Whoops, unknown action! Bail.
114 Log.e(TAG, "Unknown action, exiting");
115 finish();
116 return;
117 }
118
119 // Set the layout for this activity. You can find it in res/layout/note_editor.xml
120 setContentView(R.layout.note_editor);
121
122 // The text view for our note, identified by its ID in the XML file.
123 mText = (EditText) findViewById(R.id.note);
124
125 // Get the note!
126 mCursor = managedQuery(mUri, PROJECTION, null, null, null);
127
128 // If an instance of this activity had previously stopped, we can
129 // get the original text it started with.
130 if (savedInstanceState != null) {
131 mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
132 }
133 }
134
135 @Override
136 protected void onResume() {
137 super.onResume();
138 Log.i(TAG, "Enter in NoteEditor的onResume方法");
139 // If we didn't have any trouble retrieving the data, it is now time to get at the stuff.
140 if (mCursor != null) {
141 // Make sure we are at the one and only row in the cursor.
142 mCursor.moveToFirst();
143
144 // Modify our overall title depending on the mode we are running in.
145 if (mState == STATE_EDIT) {
146 setTitle(getText(R.string.title_edit));
147 } else if (mState == STATE_INSERT) {
148 setTitle(getText(R.string.title_create));
149 }
150
151 // This is a little tricky: we may be resumed after previously being
152 // paused/stopped. We want to put the new text in the text view,
153 // but leave the user where they were (retain the cursor position
154 // etc). This version of setText does that for us.
155 String note = mCursor.getString(COLUMN_INDEX_NOTE);//显示title的数据列的信息
156 mText.setTextKeepState(note);
157
158 // If we hadn't previously retrieved the original text, do so
159 // now. This allows the user to revert their changes.
160 if (mOriginalContent == null) {
161 mOriginalContent = note;
162 }
163 Log.i(TAG, "Enter in NoteEditor的onResume方法后,当前mOriginalContent的值为:"+mOriginalContent);
164 } else {
165 setTitle(getText(R.string.error_title));
166 mText.setText(getText(R.string.error_message));
167 }
168 }
169
170 @Override
171 /**
172 * 运用onPause()和onSaveInstanceState保存数据 ,对这两个方法的使用进行讲解
173 * 参考:http://dev.10086.cn/cmdn/wiki/index.php?edition-view-6259-1.html
174 * 本例在测试中调用该方法的情景为:在编辑Note时,直接按home键回到首页,就先执行onSaveInstanceState方法,然后执行onPause方法。
175 * 再次打开程序时会直接进入刚刚编辑note的NoteEditor界面,因为在离开程序时,底层Activity.class会执行该方法,保持当前程序退出的状态:
176 * final void performSaveInstanceState(Bundle outState) {
177 * onSaveInstanceState(outState);
178 * saveManagedDialogs(outState);
179 * }
180 */
181 protected void onSaveInstanceState(Bundle outState) {
182 // Save away the original text, so we still have it if the activity
183 // needs to be killed while paused.
184 //界面销毁之前保存数据
185 Log.i(TAG, "Enter in NoteEditor的onSaveInstanceState方法后,当前mOriginalContent的值为:"+mOriginalContent);
186 outState.putString(ORIGINAL_CONTENT, mOriginalContent);
187 }
188
189 @Override
190 protected void onPause() {
191 super.onPause();
192 //界面失去控制权时保存数据
193 Log.i(TAG, "Enter in NoteEditor的onPause方法");
194 // The user is going somewhere else, so make sure their current
195 // changes are safely saved away in the provider. We don't need
196 // to do this if only editing.
197 if (mCursor != null) {
198 String text = mText.getText().toString();
199 int length = text.length();
200
201 // If this activity is finished, and there is no text, then we
202 // do something a little special: simply delete the note entry.
203 // Note that we do this both for editing and inserting... it
204 // would be reasonable to only do it when inserting.
205 if (isFinishing() && (length == 0) && !mNoteOnly) {
206 setResult(RESULT_CANCELED);
207 deleteNote();
208
209 // Get out updates into the provider.
210 } else {
211 ContentValues values = new ContentValues();
212
213 // This stuff is only done when working with a full-fledged note.
214 if (!mNoteOnly) {
215 // Bump the modification time to now.
216 values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());
217
218 // If we are creating a new note, then we want to also create
219 // an initial title for it.
220 if (mState == STATE_INSERT) {
221 String title = text.substring(0, Math.min(30, length));
222 if (length > 30) {
223 int lastSpace = title.lastIndexOf(' ');
224 if (lastSpace > 0) {
225 title = title.substring(0, lastSpace);
226 }
227 }
228 values.put(Notes.TITLE, title);
229 }
230 }
231
232 // Write our text back into the provider.
233 values.put(Notes.NOTE, text);
234
235 // Commit all of our changes to persistent storage. When the update completes
236 // the content provider will notify the cursor of the change, which will
237 // cause the UI to be updated.
238 getContentResolver().update(mUri, values, null, null);
239 }
240 }
241 }
242
243 @Override
244 public boolean onCreateOptionsMenu(Menu menu) {
245 super.onCreateOptionsMenu(menu);
246 Log.i(TAG, "Enter in NoteEditor的onCreateOptionsMenu方法");
247 // Build the menus that are shown when editing.
248 if (mState == STATE_EDIT) {
249 menu.add(0, REVERT_ID, 0, R.string.menu_revert)
250 .setShortcut('0', 'r')
251 .setIcon(android.R.drawable.ic_menu_revert);
252 if (!mNoteOnly) {
253 menu.add(0, DELETE_ID, 0, R.string.menu_delete)
254 .setShortcut('1', 'd')
255 .setIcon(android.R.drawable.ic_menu_delete);
256 }
257
258 // Build the menus that are shown when inserting.
259 } else {
260 menu.add(0, DISCARD_ID, 0, R.string.menu_discard)
261 .setShortcut('0', 'd')
262 .setIcon(android.R.drawable.ic_menu_delete);
263 }
264
265 // If we are working on a full note, then append to the
266 // menu items for any other activities that can do stuff with it
267 // as well. This does a query on the system for any activities that
268 // implement the ALTERNATIVE_ACTION for our data, adding a menu item
269 //for each one that is found.
270 //这里使用动态的方法出创建菜单项
271 if (!mNoteOnly) {
272 Intent intent = new Intent(null, getIntent().getData());
273 intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
274 menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
275 new ComponentName(this, NoteEditor.class), null, intent, 0, null);
276 }
277 /* 注意这里就会产生一个问题,效果中menuItem的菜单项如何显示,即如何找到我们对于string里的常量的,
278 经过测试后,发现它会默认先找:
279 <intent-filter android:label="@string/resolve_title">
280 这个label标签,找不到的话,就会往上找,
281 <activity android:name="TitleEditor" android:label="@string/title_edit_title"
282 就会找到这个activity的label显示
283 */
284
285 return true;
286 }
287
288 @Override
289 public boolean onOptionsItemSelected(MenuItem item) {
290 Log.i(TAG, "Enter in NoteEditor的onOptionsItemSelected方法");
291 // Handle all of the possible menu actions.
292 switch (item.getItemId()) {
293 case DELETE_ID:
294 deleteNote();
295 finish();
296 break;
297 case DISCARD_ID:
298 cancelNote();
299 break;
300 case REVERT_ID:
301 cancelNote();
302 break;
303 }
304 return super.onOptionsItemSelected(item);
305 }
306
307 /**
308 * Take care of canceling work on a note. Deletes the note if we
309 * had created it, otherwise reverts to the original text.
310 */
311 private final void cancelNote() {
312 Log.i(TAG, "Enter in NoteEditor的cancelNote方法");
313 if (mCursor != null) {
314 if (mState == STATE_EDIT) {
315 // Put the original note text back into the database
316 mCursor.close();
317 mCursor = null;
318 ContentValues values = new ContentValues();
319 values.put(Notes.NOTE, mOriginalContent);
320 getContentResolver().update(mUri, values, null, null);
321 } else if (mState == STATE_INSERT) {
322 // We inserted an empty note, make sure to delete it
323 deleteNote();
324 }
325 }
326 setResult(RESULT_CANCELED);
327 finish();
328 }
329
330 /**
331 * Take care of deleting a note. Simply deletes the entry.
332 */
333 private final void deleteNote() {
334 Log.i(TAG, "Enter in NoteEditor的deleteNote方法");
335 if (mCursor != null) {
336 mCursor.close();
337 mCursor = null;
338 getContentResolver().delete(mUri, null, null);
339 mText.setText("");
340 }
341 }
342 }
这个类的最大的特点就是重载了组件EditText!效果如下:

每行字都加了下划线,解释一下实现原理(注:这篇文章主要讲解这里重载)
主要的方法就是:
1 @Override
2 protected void onDraw(Canvas canvas) {
3 int count = getLineCount();
4 Rect r = mRect;
5 Paint paint = mPaint;
6
7 for (int i = 0; i < count; i++) {
8 int baseline = getLineBounds(i, r);
9 canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
10 }
11 super.onDraw(canvas);
12 }
这里调用了两个EditText自带的两个方法,一个是getLineCount():获取总的行数,一个是getLineBounds():传入的参数第一个是表示第几行,第二个参数是第N行所占的Rect,然后以此为标准进行画线。
根据我的猜测:
1)因为是继承的EditText,所以会在Focus状态下自动调出键盘;
2)每次字符变换都会自动调用onDraw方法重绘界面,这种情况下,不论你是换行还是删除字符,都能保证写到哪一行就画线画到哪一行,也就会形成上面的那种效果。

浙公网安备 33010602011771号