【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方法重绘界面,这种情况下,不论你是换行还是删除字符,都能保证写到哪一行就画线画到哪一行,也就会形成上面的那种效果。

 

posted @ 2012-12-06 19:24  大脚印  阅读(468)  评论(0编辑  收藏  举报