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