[안드로이드] 이미 존재하는 뷰를 수정하기

반응형




이어지는 글     
커스텀 컴포넌트 만들기       
이미 존재하는 뷰를 수정하기    ◀ 현재 위치

읽기전에 손가락 한번 클릭~ >_<

고마워요 ~ Chu ~ ♥


안드로이드의 커스텀 컴포넌트에 관한 글입니다.


이미 존재하는 뷰를 수정하기

만약 원하는 것과 비슷하지만.. 조금만 다른 컴포넌트가 있다면 좀 더 손쉽게 하기 위하여 해당하는 그 컴포넌트를 상속하고 수정할 부분만 오버라이드 하여 사용하면 되겠죠.

이것은 뷰 계층구조에 알맞은 클래스를 사용함으로써 유용합니다.

이미 존재하는 뷰를 수정하는 예제
예제로서는 샘플 코드의 노트패트 샘플에 보면 EditText 뷰를 확장하여 밑줄이 있는 노트를 만든 것이 있습니다. 이것은 NoteEditor.java 파일 안에 있는 LineEditText 클래스를 보시면 됩니다.

/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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 com.example.android.notepad;
import com.example.android.notepad.NotePad.Notes;
import android.app.Activity;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
/**
* A generic activity for editing a note in a database. This can be used
* either to simply view a note {@link Intent#ACTION_VIEW}, view and edit a note
* {@link Intent#ACTION_EDIT}, or create a new note {@link Intent#ACTION_INSERT}.
*/
public class NoteEditor extends Activity {
private static final String TAG = "Notes";
/**
* Standard projection for the interesting columns of a normal note.
*/
private static final String[] PROJECTION = new String[] {
Notes._ID, // 0
Notes.NOTE, // 1
};
/** The index of the note column */
private static final int COLUMN_INDEX_NOTE = 1;
// This is our state data that is stored when freezing.
private static final String ORIGINAL_CONTENT = "origContent";
// Identifiers for our menu items.
private static final int REVERT_ID = Menu.FIRST;
private static final int DISCARD_ID = Menu.FIRST + 1;
private static final int DELETE_ID = Menu.FIRST + 2;
// The different distinct states the activity can be run in.
private static final int STATE_EDIT = 0;
private static final int STATE_INSERT = 1;
private int mState;
private boolean mNoteOnly = false;
private Uri mUri;
private Cursor mCursor;
private EditText mText;
private String mOriginalContent;
/**
* A custom EditText that draws lines between each line of text that is displayed.
*/
public static class LinedEditText extends EditText {
private Rect mRect;
private Paint mPaint;
// we need this constructor for LayoutInflater
public LinedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0x800000FF);
}
@Override
protected void onDraw(Canvas canvas) {
int count = getLineCount();
Rect r = mRect;
Paint paint = mPaint;
for (int i = 0; i < count; i++) {
int baseline = getLineBounds(i, r);
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
}
super.onDraw(canvas);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
// Do some setup based on the action being performed.
final String action = intent.getAction();
if (Intent.ACTION_EDIT.equals(action)) {
// Requested to edit: set that state, and the data being edited.
mState = STATE_EDIT;
mUri = intent.getData();
} else if (Intent.ACTION_INSERT.equals(action)) {
// Requested to insert: set that state, and create a new entry
// in the container.
mState = STATE_INSERT;
mUri = getContentResolver().insert(intent.getData(), null);
// If we were unable to create a new note, then just finish
// this activity. A RESULT_CANCELED will be sent back to the
// original activity if they requested a result.
if (mUri == null) {
Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
finish();
return;
}
// The new entry was created, so assume all will end well and
// set the result to be returned.
setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
} else {
// Whoops, unknown action! Bail.
Log.e(TAG, "Unknown action, exiting");
finish();
return;
}
// Set the layout for this activity. You can find it in res/layout/note_editor.xml
setContentView(R.layout.note_editor);
// The text view for our note, identified by its ID in the XML file.
mText = (EditText) findViewById(R.id.note);
// Get the note!
mCursor = managedQuery(mUri, PROJECTION, null, null, null);
// If an instance of this activity had previously stopped, we can
// get the original text it started with.
if (savedInstanceState != null) {
mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
}
}
@Override
protected void onResume() {
super.onResume();
// If we didn't have any trouble retrieving the data, it is now
// time to get at the stuff.
if (mCursor != null) {
// Make sure we are at the one and only row in the cursor.
mCursor.moveToFirst();
// Modify our overall title depending on the mode we are running in.
if (mState == STATE_EDIT) {
setTitle(getText(R.string.title_edit));
} else if (mState == STATE_INSERT) {
setTitle(getText(R.string.title_create));
}
// This is a little tricky: we may be resumed after previously being
// paused/stopped. We want to put the new text in the text view,
// but leave the user where they were (retain the cursor position
// etc). This version of setText does that for us.
String note = mCursor.getString(COLUMN_INDEX_NOTE);
mText.setTextKeepState(note);
// If we hadn't previously retrieved the original text, do so
// now. This allows the user to revert their changes.
if (mOriginalContent == null) {
mOriginalContent = note;
}
} else {
setTitle(getText(R.string.error_title));
mText.setText(getText(R.string.error_message));
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// Save away the original text, so we still have it if the activity
// needs to be killed while paused.
outState.putString(ORIGINAL_CONTENT, mOriginalContent);
}
@Override
protected void onPause() {
super.onPause();
// The user is going somewhere else, so make sure their current
// changes are safely saved away in the provider. We don't need
// to do this if only editing.
if (mCursor != null) {
String text = mText.getText().toString();
int length = text.length();
// If this activity is finished, and there is no text, then we
// do something a little special: simply delete the note entry.
// Note that we do this both for editing and inserting... it
// would be reasonable to only do it when inserting.
if (isFinishing() && (length == 0) && !mNoteOnly) {
setResult(RESULT_CANCELED);
deleteNote();
// Get out updates into the provider.
} else {
ContentValues values = new ContentValues();
// This stuff is only done when working with a full-fledged note.
if (!mNoteOnly) {
// Bump the modification time to now.
values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());
// If we are creating a new note, then we want to also create
// an initial title for it.
if (mState == STATE_INSERT) {
String title = text.substring(0, Math.min(30, length));
if (length > 30) {
int lastSpace = title.lastIndexOf(' ');
if (lastSpace > 0) {
title = title.substring(0, lastSpace);
}
}
values.put(Notes.TITLE, title);
}
}
// Write our text back into the provider.
values.put(Notes.NOTE, text);
// Commit all of our changes to persistent storage. When the update completes
// the content provider will notify the cursor of the change, which will
// cause the UI to be updated.
getContentResolver().update(mUri, values, null, null);
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// Build the menus that are shown when editing.
if (mState == STATE_EDIT) {
menu.add(0, REVERT_ID, 0, R.string.menu_revert)
.setShortcut('0', 'r')
.setIcon(android.R.drawable.ic_menu_revert);
if (!mNoteOnly) {
menu.add(0, DELETE_ID, 0, R.string.menu_delete)
.setShortcut('1', 'd')
.setIcon(android.R.drawable.ic_menu_delete);
}
// Build the menus that are shown when inserting.
} else {
menu.add(0, DISCARD_ID, 0, R.string.menu_discard)
.setShortcut('0', 'd')
.setIcon(android.R.drawable.ic_menu_delete);
}
// If we are working on a full note, then append to the
// menu items for any other activities that can do stuff with it
// as well. This does a query on the system for any activities that
// implement the ALTERNATIVE_ACTION for our data, adding a menu item
// for each one that is found.
if (!mNoteOnly) {
Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
new ComponentName(this, NoteEditor.class), null, intent, 0, null);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle all of the possible menu actions.
switch (item.getItemId()) {
case DELETE_ID:
deleteNote();
finish();
break;
case DISCARD_ID:
cancelNote();
break;
case REVERT_ID:
cancelNote();
break;
}
return super.onOptionsItemSelected(item);
}
/**
* Take care of canceling work on a note. Deletes the note if we
* had created it, otherwise reverts to the original text.
*/
private final void cancelNote() {
if (mCursor != null) {
if (mState == STATE_EDIT) {
// Put the original note text back into the database
mCursor.close();
mCursor = null;
ContentValues values = new ContentValues();
values.put(Notes.NOTE, mOriginalContent);
getContentResolver().update(mUri, values, null, null);
} else if (mState == STATE_INSERT) {
// We inserted an empty note, make sure to delete it
deleteNote();
}
}
setResult(RESULT_CANCELED);
finish();
}
/**
* Take care of deleting a note. Simply deletes the entry.
*/
private final void deleteNote() {
if (mCursor != null) {
mCursor.close();
mCursor = null;
getContentResolver().delete(mUri, null, null);
mText.setText("");
}
}
}
view raw gistfile1.java hosted with ❤ by GitHub



이것도 좀 뜯어 보자면,
1. 클래스를 정의합니다. ( public static LineEditText extends EditText )
◎ 이것은 NoteEditor 액티비티 내의 내부클래스로 정의되어 있고, 외부에서 필요시 접근할 수 있도록 하기 위해 public 접근자를 가지네요. ( 외부에선 NoteEditor.LineEditText 로 접근 ) . 또한 static 으로 선언되어 있기 때문에 부모클래스로부터 데이터 접근에 대한 메소드가 없다는걸 의미합니다.

이것은 NoteEditor 클래스와 강하게 연결된 클래스 라기 보다는, 분리된 클래스로써 동작함을 의미하는데요, 이런 방법은 외부로부터 메소드에 접근이 없는 내부클래스를 만드는 좋은 방법이며, 작고 , 다른 클래스로부터 쉽게 사용될 수 있다는 장점이 있습니다.

EditText 를 상속합니다. 상속하여 커스텀 뷰를 만들고, 만들어진 뷰를 기존의 것과 대체해 버립니다.


2. 클래스 초기화를 설정합니다. 이때 super 생성자를 먼저 호출해 줍니다. ( 디폴트 생성자가 아닌 인자를 가지는 생성자 )
◎ EditText 는 XML 로부터 전개될 때 이러한 파라미터를 가지고 생성되는데요, 이때 생성자는 파라미터를 얻고 슈퍼클래스의 생성자를 호출하는 두 가지의 일을 해야 합니다.


3. 메소드를 오버라이드 합니다 . 이 예제에선 onDraw() 메소드만 오버라이드 했네요.
◎ onDraw() 를 오버라이드 하여 EditText 뷰 캔버스에 선을 그릴수 있게 되었습니다. 또한 캔버스는 오버라이드 된 onDraw() 에 전달됩니다.

-> 슈퍼클래스 메소드인 super.onDraw() 는 무조건 호출해야되는데, 이때 onDraw() 메소드가 끝나기전에 호출해야 됩니다. 편의상 모든 그리기를 끝낸 후에 마지막에 호출했네요


4. 커스텀 컴포넌트를 사용합니다. 위에처럼 만들어진 커스텀 컴포넌트를 노트패드에서는 레이아웃에서 직접 사용합니다. res/layout 의 note_editor.xml 을 보면 이렇게 되어 있군요.
<view
class="com.android.notepad.NoteEditor$MyEditText"
id="@+id/note"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:drawable/empty"
android:padding="10dip"
android:scrollbars="vertical"
android:fadingEdge="vertical" />
view raw gistfile1.xml hosted with ❤ by GitHub


커스텀 컴포넌트는 XML 에서 일반 뷰로 생성됩니다. 또 해당 클래스는 패키지명을 모두 적어주어야만 하는데요. 내부 클래스를 참조할 때 쓰인 ' NoteEditor$LineEditText ' 는 자바에서 내부클래스를 참조할 때 쓰는 표준 방법 입니다.


만약, 커스텀 컴포넌트가 내부클래스가 아니라면 Class 속성을 빼야겠죠. 또 XML 엘리먼트 이름을 가진 뷰로 선언해야 합니다.
<com.android.notepad.MyEditText
id="@+id/note"
<!-- 구현부분 -->
/>
view raw gistfile1.xml hosted with ❤ by GitHub



이제 이 안에 있는 속성과 파라미터들은 커스텀 컴포넌트 생성자에게 전달될 것입니다. 결국 EditText 생성자에도 전달이 되겠지요.
그러므로 이것은 EditText 뷰에서 사용하는 파라미터들과 동일합니다.
그리고 자신만의 파라미터를 추가하는 것도 가능하기 때문에 커스텀 컴포넌트의 장점을 살릴 수 있겠네요~





커스텀 컴포넌트에 관한 글이었습니다.

 


반응형

댓글

작가 남시언님의
글이 좋았다면 응원을 보내주세요!

Designed by JB FACTORY