출처 : http://hmkcode.com/android-custom-listview-titles-icons-counter/

Android | Custom ListView with Titles, Icons & Counter

android-pro-listviewYou can customize the ListView by providing a new layout for the list items “rows”. The new layout can be more than just a single TextView. Here will see how to develop a custom ListView with items having leading icons “ImageView“, trailing counters “TextView” and some text in between.  Also, we will see how to add items that will act as a group header “title” for a set of other items.

 

 

Objectives:

  • How to create ListView with custom View?
  • How to create ListView items with leading icons “ImageView” and trailing counters?
  • How to create ListView items that act as header of group of items?

Environment & Tools:

  • Android Developer Tools (ADT) (or Eclipse + ADT plugin)
  • AVD Nexus S Android 4.3 “emulator” or,
  • Min SDK 8

( 1 ) Create Layout “UI” for ListView Items

We will define the items layout. We need two different layouts one for the target item and one for the group header item.

1. target_item.xml

2. group_header_item.xml

1. Target Item

  • res/layout/target_item.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:background="#F3F3F3">
  
     <!-- icon -->
     <ImageView
         android:id="@+id/item_icon"
         android:layout_width="32dp"
         android:layout_height="32dp"
         android:layout_alignParentLeft="true"
         android:layout_marginLeft="8dp"
         android:layout_marginRight="8dp"
         android:layout_marginTop="8dp"
         android:src="@drawable/action_help"
         />
  
    <!-- title -->
    <TextView
         android:id="@+id/item_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_toRightOf="@+id/item_icon"
         android:layout_alignBaseline="@+id/item_counter"
         android:textSize="18dp" />
  
        <!-- counter -->
        <TextView
            android:id="@+id/item_counter"
            android:layout_width="32dp"
            android:layout_height="32dp"
            android:layout_alignParentRight="true"
            android:layout_marginRight="8dp"
            android:layout_marginTop="8dp"
            android:background="@drawable/rectangle"
            android:gravity="center"
            android:textColor="#FFFFFF"
            android:textSize="12sp"
            android:textStyle="bold" />
  
</RelativeLayout>

Note: @drawable/rectangle is defined in res/drawable/rectangle.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
   <solid android:color="#5490CC"></solid>
   <corners android:radius="8px"></corners
   <stroke  android:width="2dp" android:color="#A4C2E0"></stroke>  
</shape>

2. Group Header Item

  • res/layout/group_header_item.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp"
  
    android:background="#F3F3F3">
  
    <!-- title -->
    <TextView
         android:id="@+id/header"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:gravity="center_vertical"
         android:textSize="16dp"
         android:layout_marginLeft="12dp"
         android:layout_marginBottom="4dp"
         android:layout_alignParentBottom="true" />
  
    <!--  divider -->
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_marginBottom="1dp"
        android:layout_alignParentBottom="true"
        android:background="#DADADC" ></View>
  
</RelativeLayout>

( 2 ) Model.java Class “Model”

  • Create a model class and call it “Model.java” to hold data to be displayed on list items.
  • We need to define an object of type Model to hold the resources “data” for each list item.
  • The way we define the Model object “which constructor we use” will determine which list item layout “target item or group header item” will be displayed. Basically we set the a boolean property “isGroupHeader” to be always false “if we are creating the Model object for target item layout“ unless we define the Model object “for group header item layout” using the single argument constructor then isGroupHeader will be true.
  • For target_item we need a Model object to be declared as following:
new Model(R.drawable.action_help_32,"Menu Item 1","1")
//R.drawable.action_help_32 is a png image in res/drawable folder

  • For group_header_item we need a Model object to be declared as following:
new Model("Group Title")

  • src/com/hmkcode/android/Model.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.hmkcode.android;
  
public class Model{
  
    private int icon;
    private String title;
    private String counter;
  
    private boolean isGroupHeader = false;
  
    public Model(String title) {
        this(-1,title,null);
        isGroupHeader = true;
    }
    public Model(int icon, String title, String counter) {
        super();
        this.icon = icon;
        this.title = title;
        this.counter = counter;
    }
  
//gettters & setters...
}

( 3 ) MyAdapter.java

  • MyAdpater is the class that will fill the gap between step 1 & 2 i.e. the item layout and Model data.
  • MyAdapter extends ArrayAdapter<Model>
  • MyAdapter(Context context, ArrayList<Model> modelsArrayList) constructor takes context i.e. “Activity” & array of objects of type Model.
  • Override getView() method
      • getView() method will basically go over the passed array of objects “Model”
      • If the Model object isGroupHeader = false then inflate target_item, get its views, set their values and return View with target_layout
      • else return View with group_header_layout.
  • src/com/hmkcode/android/MyAdapter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.hmkcode.android;
  
import java.util.ArrayList;
  
import com.hmkcode.android.R;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
  
public class MyAdapter extends ArrayAdapter<Model> {
  
        private final Context context;
        private final ArrayList<Model> modelsArrayList;
  
        public MyAdapter(Context context, ArrayList<Model> modelsArrayList) {
  
            super(context, R.layout.target_item, modelsArrayList);
  
            this.context = context;
            this.modelsArrayList = modelsArrayList;
        }
  
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
  
            // 1. Create inflater 
            LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  
            // 2. Get rowView from inflater
  
            View rowView = null;
            if(!modelsArrayList.get(position).isGroupHeader()){
                rowView = inflater.inflate(R.layout.target_item, parent, false);
  
                // 3. Get icon,title & counter views from the rowView
                ImageView imgView = (ImageView) rowView.findViewById(R.id.item_icon); 
                TextView titleView = (TextView) rowView.findViewById(R.id.item_title);
                TextView counterView = (TextView) rowView.findViewById(R.id.item_counter);
  
                // 4. Set the text for textView 
                imgView.setImageResource(modelsArrayList.get(position).getIcon());
                titleView.setText(modelsArrayList.get(position).getTitle());
                counterView.setText(modelsArrayList.get(position).getCounter());
            }
            else{
                    rowView = inflater.inflate(R.layout.group_header_item, parent, false);
                    TextView titleView = (TextView) rowView.findViewById(R.id.header);
                    titleView.setText(modelsArrayList.get(position).getTitle());
  
            }
  
            // 5. retrn rowView
            return rowView;
        }
}

( 4 ) MainActivity.java

  • For MainActivity class we have two options either to extend Activity or ListActivity, comments on the code explain how to use each one.
  • src/com/hmkcode/android/MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.hmkcode.android;
  
import java.util.ArrayList;
  
import android.app.Activity;
import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ListView;
  
public class MainActivity extends ListActivity {
  
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
  
        // if extending Activity
        //setContentView(R.layout.activity_main);
  
        // 1. pass context and data to the custom adapter
        MyAdapter adapter = new MyAdapter(this, generateData());
  
        // if extending Activity 2. Get ListView from activity_main.xml
        //ListView listView = (ListView) findViewById(R.id.listview);
  
        // 3. setListAdapter
        //listView.setAdapter(adapter); if extending Activity
        setListAdapter(adapter);
    }
  
    private ArrayList<Model> generateData(){
        ArrayList<Model> models = new ArrayList<Model>();
        models.add(new Model("Group Title"));
        models.add(new Model(R.drawable.action_help_32,"Menu Item 1","1"));
        models.add(new Model(R.drawable.action_search_32,"Menu Item 2","2"));
        models.add(new Model(R.drawable.collections_cloud_32,"Menu Item 3","12"));
  
        return models;
    }
  
}

Run the app you will get the following UI

Icons used in this sample are downloaded from Download the Action Bar Icon Pack

Source Code @ GitHub

출처 : http://www.kmshack.kr/323

 

Android View와 관련된 오픈소스들이 많이 공개 되고 있다. 그래서 많이 쓰이고 유용한 오픈소스를 정리 해보았다.  아직 국내에서는 오픈소스가 인색한지 모두 외국에서 만든것들이다. 나도 View관련 오픈소스를 하나준비 하겠다고 준비중인데.. 준비중이기만 하다.

 

참고로, 잘 알려지고 검증된 오픈소스라도 코드를 하나하나씩 보고 어떻게 구현되어 있는지 꼭 살펴보고 썼으면 좋겠다. 그리고 이제 개발을 시작한지 얼마 안된 분들이라면 이런 오픈소스를 사용함으로 자기 실력을 죽일 수도 있으니 한번씩 구현해보는 것도 좋을듯 하다.

 

그리고 국내 개발 커뮤니티에 보면 자기가 개발할 것에 대해 오픈소스를 찾음으로써 개발해서 삽질 하기전에 찾아서 다행이다라는 글들을 보면 씁쓸하다. 오픈소스는 좋은점도 있지만 그에 반하는 양면성을 가지고 있다는 것에 조심했으면 좋겠다.

 

 

아무튼 아래것들은 이미 유명하고 잘만들어진 오픈소스들이다.

 

 

 

 

1. ActionbarSherlock

허니컴부터 적용된 액션바를 이전버전에도 사용할 수 있게 해준다.

 

http://www.actionbarsherlock.com/
https://github.com/JakeWharton/ActionBarSherlock

 

ActionBarSherlock is an standalone library designed to facilitate the use of the action bar design pattern across all versions of Android through a single API.

The library will automatically use the native ActionBar implementation on Android 4.0 or later. For previous versions which do not include ActionBar, a custom action bar implementation based on the sources of Ice Cream Sandwich will automatically be wrapped around the layout. This allows you to easily develop an application with an action bar for every version of Android from 2.x and up.

 

 

 

2. Android-PullToRefresh

ListView, ScrollView, Viewpager, WebView 등 새로고침 기능의 View를 만들어 준다.

 

https://github.com/chrisbanes/Android-PullToRefresh

 

This project aims to provide a reusable Pull to Refresh widget for Android. It was originally based on Johan Nilsson's library (mainly for graphics, strings and animations), but these have been replaced since.

 

 

 

 

3. StickyListHeaders

ListView의 Section Header 정보를 넣을 수 있다.

 

https://github.com/emilsjolander/StickyListHeaders

 

StickyListHeaders is an Android library that makes it easy to integrate section headers in your ListView. These section headers stick to the top like in the new People app of Android 4.0 Ice Cream Sandwich. This behavior is also found in lists with sections on iOS devices. This library can also be used for without the sticky functionality if you just want section headers.

StickyListHeaders actively supports android versions 2.3 (gingerbread) and above That said, it should be compatible with much older versions of android as well but these are not actively tested.

Here is a short gif showing the functionality you get with this library:

 

 


4. MenuDrawer
좌측 또는 우측의 슬라이드 메뉴를 구성 할 수 있다.


https://github.com/SimonVT/android-menudrawer

 

A slide-out menu implementation, which allows users to navigate between views in your app. Most commonly the menu is revealed by either dragging the edge of the screen, or clicking the 'up' button in the action bar.

 

 


5. SlidingMenu
MenuDrawer와 같다.

 

https://github.com/jfeinstein10/SlidingMenu


SlidingMenu is an Open Source Android library that allows developers to easily create applications with sliding menus like those made popular in the Google+, YouTube, and Facebook apps. Feel free to use it all you want in your Android apps provided that you cite this project and include the license in your app.]

 

 


6. FadingActionBar

리스트뷰 스크롤시 헤더 컨텐츠에 따라 액션바의 알파값이 변한다. 구글 음악플레이어의 아티스트정보 페이지

 

https://github.com/ManuelPeinado/FadingActionBar

 

FadingActionBar is a library which implements the cool fading action bar effect that can be seen in the new Play Music app.

 


7. DragSortListView

리스트 소팅

 

https://github.com/bauerca/drag-sort-listview

 

DragSortListView (DSLV) is an extension of the Android ListView that enables drag-and-drop reordering of list items.

 

 


8. IndexableListView

리스트뷰의 알파벳 인덱스 기능

 

https://github.com/woozzu/IndexableListView

 

 


9. ListViewAnimations

ListView 스크롤시 애니메이션(구글 플러스)

 

https://github.com/nhaarman/ListViewAnimations

 

ListViewAnimations is an Open Source Android library that allows developers to easily create ListViews with animations. Feel free to use it all you want in your Android apps provided that you cite this project and include the license in your app.

 

 


10. ViewPagerIndicator

ViewPager 인디케이터

 

https://github.com/JakeWharton/Android-ViewPagerIndicator

 

Android-ViewPagerIndicator is presented as an Android library project. A standalone JAR is not possible due to the theming capabilities offered by the indicator widgets.


 


11. PagerSlidingTabStrip

ViewPager 인디케이터

 

https://github.com/astuetz/PagerSlidingTabStrip

 

Interactive paging indicator widget, compatible with the ViewPager from the Android Support Library.

 

 


12. JazzyViewPager

ViewPager 스크롤시 애니메이션

 

https://github.com/jfeinstein10/JazzyViewPager

 

An easy to use ViewPager that adds an awesome set of custom swiping animations. Just change your ViewPagers to JazzyViewPagers and you're good to go!

 

 


14. ViewPager3D

ViewPager 스크롤시 3D 효과

 

https://github.com/inovex/ViewPager3D

 

 


15. DirectionalViewPager

ViewPager 좌우, 아래위 스크롤

 

https://github.com/JakeWharton/Android-DirectionalViewPager

 

Implementation of the compatibility library ViewPager class that supports paging both vertically and horizontally as well as changing between the two at runtime.

 

 

 

16. VerticalSeekBarAndroid
수직 SeekBar

 

https://github.com/AndroSelva/Vertical-SeekBar-Android

 

This project is all about Customizing the normal SeekBar to Vertical Seekbar.

 

 


17. HoloCircleSeekBar
원형으로 생긴 SeekBar

 

https://github.com/JesusM/HoloCircleSeekBar

 

A Circle SeekBar inspired by Android Holo ColorPicker designed by Marie Schweiz and developed by Lars Werkman.

 

 


18. MultiChoiceAdapter
ListView, GridView에서 다중선택을 쉽게 할 수 있도록 도와준다.

 

https://github.com/ManuelPeinado/MultiChoiceAdapter

 

MultiChoiceAdapter is an implementation of ListAdapter which adds support for modal multiple choice selection as in the native Gmail app.

 

 


19. TwoWayGridView
GridView를 가로, 세로방향으로 스크롤 되도록 한다.

 

https://github.com/jess-anders/two-way-gridview

 

An Android GridView that can be configured to scroll horizontally or vertically.

 

 


20. ScrollBarPanel
스크롤바 옆에 View를 생성(Path 2.0에서 볼 수 있는 기능)

 

https://github.com/rno/Android-ScrollBarPanel

 

Android-ScrollBarPanel allows to attach a View to a scroll indicator like it's done in Path 2.0.

출처 : http://kahdev.wordpress.com/2008/09/13/making-a-custom-android-button-using-a-custom-view/

 

Creating a custom view is as simple as inheriting from the View class and overriding the methods that need to be overridden. In this example, a custom button is implemented in this way. The button shall feature a labelled image (i.e. an image with text underneath).



1   public class CustomImageButton extends View {
2       private final static int WIDTH_PADDING = 8;
3       private final static int HEIGHT_PADDING = 10;
4       private final String label;
5       private final int imageResId;
6       private final Bitmap image;
7       private final InternalListener listenerAdapter = new InternalListener();
8

The constructor can take in the parameters to set the button image and label.


9       /**
10       * Constructor.
11       *
12       * @param context
13       *        Activity context in which the button view is being placed for.
14       *
15       * @param resImage
16       *        Image to put on the button. This image should have been placed
17       *        in the drawable resources directory.
18       *
19       * @param label
20       *        The text label to display for the custom button.
21       */
22      public CustomImageButton(Context context, int resImage, String label) 
23       {
24           super(context);
25           this.label = label;
26           this.imageResId = resImage;
27           this.image = BitmapFactory.decodeResource(context.getResources(),
28                  imageResId);
29  
30           setFocusable(true);
31           setBackgroundColor(Color.WHITE);
32
33           setOnClickListener(listenerAdapter);
34           setClickable(true);
35       }
36

With the constructor defined, there are a number of methods in the View class that needs to be overridden to this view behave like a button. Firstly, the onFocusChanged gets triggered when the focus moves onto or off the view. In the case of our custom button, we want the button to be “highlighted” when ever the focus is on the button.


37       /**
38       * The method that is called when the focus is changed to or from this
39       * view.
40       */
41       protected void onFocusChanged(boolean gainFocus, int direction,
42                      Rect previouslyFocusedRect)
43       {
44           if (gainFocus == true)
45           {
46               this.setBackgroundColor(Color.rgb(255, 165, 0));
47           }
48           else
49          {
50              this.setBackgroundColor(Color.WHITE);
51          }
52      }
53      

The method responsible for rendering the contents of the view to the screen is the draw method. In this case, it handles placing the image and text label on to the custom view.


54      /**
55       * Method called on to render the view.
56       */
57      protected void onDraw(Canvas canvas)
58      {
59          Paint textPaint = new Paint();
60          textPaint.setColor(Color.BLACK);
61          canvas.drawBitmap(image, WIDTH_PADDING / 2, HEIGHT_PADDING / 2, null);
62          canvas.drawText(label, WIDTH_PADDING / 2, (HEIGHT_PADDING / 2) +
63                  image.getHeight() + 8, textPaint);
64      }
65      

For the elements to be displayed correctly on the screen, Android needs to know the how big the custom view is. This is done through overriding the onMeasure method. The measurement specification parameters represent dimension restrictions that are imposed by the parent view.


 66        @Override
 67        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
 68        {
 69         setMeasuredDimension(measureWidth(widthMeasureSpec),
 70                 measureHeight(heightMeasureSpec));
 71        }
 72    

The call to setMeasuredDimension in the onMeasure method is important. The documentation states that the call is necessary to avoid a IllegalStateException.


 73     private int measureWidth(int measureSpec)
 74     {
 75         int preferred = image.getWidth() * 2;
 76         return getMeasurement(measureSpec, preferred);
 77     }
 78    
 79     private int measureHeight(int measureSpec)
 80     {
 81         int preferred = image.getHeight() * 2;
 82         return getMeasurement(measureSpec, preferred);
 83     }
 84    

To calculate the width and height measurements, I’ve chosen to keep the logic simple by using a simple formula to calculate the dimensions. This simple formula computes the dimensions based on the dimensions of the image. The measureSpec parameter specifies what restrictions are imposed by the parent layout.


 85     private int getMeasurement(int measureSpec, int preferred)
 86     {
 87         int specSize = MeasureSpec.getSize(measureSpec);
 88         int measurement = 0;
 89        
 90         switch(MeasureSpec.getMode(measureSpec))
 91         {
 92             case MeasureSpec.EXACTLY:
 93                 // This means the width of this view has been given.
 94                 measurement = specSize;
 95                 break;
 96             case MeasureSpec.AT_MOST:
 97                 // Take the minimum of the preferred size and what
 98                 // we were told to be.
 99                 measurement = Math.min(preferred, specSize);
100                 break;
101             default:
102                 measurement = preferred;
103                 break;
104         }
105    
106         return measurement;
107     }
108

To make the customised button useful, it needs to trigger some kind of action when it is clicked (i.e. a listener). The view class already defines methods for setting the listener, but a more specialised listener could be better suited to the custom button. For example, the specialised listener could pass back information on the instance of the custom button.


109     /**
110      * Sets the listener object that is triggered when the view is clicked.
111      *
112      * @param newListener
113      *        The instance of the listener to trigger.
114      */
115     public void setOnClickListener(ClickListener newListener)
116     {
117         listenerAdapter.setListener(newListener);
118     }
119    

If the custom listener passes information about this instance of the custom button, it may as well have accessors so listener implementation can get useful information about this custom button.


120     /**
121      * Returns the label of the button.
122      */
123     public String getLabel()
124     {
125         return label;
126     }
127    
128     /**
129      * Returns the resource id of the image.
130      */
131     public int getImageResId()
132     {
133         return imageResId;
134     }
135    

Finally, for our custom button class that is using a custom listener, the custom listener class needs to be defined.


136     /**
137      * Internal click listener class. Translates a view’s click listener to
138      * one that is more appropriate for the custom image button class.
139      *
140      * @author Kah
141      */
142     private class InternalListener implements View.OnClickListener
143     {
144         private ClickListener listener = null;
145    
146         /**
147          * Changes the listener to the given listener.
148          *
149          * @param newListener
150          *        The listener to change to.
151          */
152         public void setListener(ClickListener newListener)
153         {
154             listener = newListener;
155         }
156        
157         @Override
158         public void onClick(View v) 
159         {
160             if (listener != null)
161             {
162                 listener.onClick(CustomImageButton.this);
163             }
164         }
165     }
166    }
167    

출처 : http://javaexpert.tistory.com/179#comment9420256

 

 기본적으로 SlidingDrawer 는 오른쪽에서 왼쪽으로 밖에 지원이 안되는가?? --

 

왼쪽에서 밀면훨씬편한데.. ㅋㅋ

 

<SlidingDrawer xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/slidingDrawer"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="center_horizontal"
    android:handle="@+id/handle"
    android:content="@+id/content"
    android:rotation="180">
    <LinearLayout android:id="@+id/handle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <ImageView android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_launcher"
            android:rotation="180" />
    </LinearLayout>
    <ImageView android:id="@+id/content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#FF0000"
        android:src="@drawable/ic_launcher"
        android:rotation="180" />
</SlidingDrawer>

 

 

이것이 xml 코드

 

 

public void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

requestWindowFeature(Window.FEATURE_NO_TITLE);

setContentView(R.layout.subware);

drawer = (SlidingDrawer)findViewById(R.id.slidingDrawer);

drawer.setOnDrawerScrollListener(new SlidingDrawer.OnDrawerScrollListener() {

@Override

public void onScrollStarted() {

// TODO Auto-generated method stub

}

@Override

public void onScrollEnded() {

// TODO Auto-generated method stub

}

});

 

drawer.setOnDrawerCloseListener(new SlidingDrawer.OnDrawerCloseListener() {

@Override

public void onDrawerClosed() {

// TODO Auto-generated method stub

}

});

drawer.setOnDrawerOpenListener(new SlidingDrawer.OnDrawerOpenListener() {

@Override

public void onDrawerOpened() {

// TODO Auto-generated method stub

}

});

}

요건 java코드

 

 

출처 :  http://blog.naver.com/PostView.nhn?blogId=rosaria1113&logNo=107988257&parentCategoryNo=&categoryNo=76&viewDate=&isShowPopularPosts=false&from=postView

 

 

 

 

펌 : http://muzesong.tistory.com/38

 

안드로이드를 하다보면 액티비티가 계속 쌓이는 경우가 있다. 이 경우는 시스템 적으로 아무래도

부담이되고, 물론 눈에 보일정도로 그런다기보다 계속 그렇게 쌓다보면 뒤로가기를 한참을 눌러야

밖으로 빠져나가게 되는 불상사가 생긴다.

이럴 경우 현재 액티비티나 현재액티비티가 아닌 다른 액티비티를 종료시켜서 액티비티를 줄이는

방법이있는데, 우선 현재 액티비티를 죽이는 방법은 간단하다.

어떠한 작업을 완료하고 액티비티를 종료하기를 원한다면 작업 완료 다음줄에

finish();


이것만 적어주면 된다. 이렇게 되면 저 함수를 만나는 즉시 현재 액티비티가 종료된다. 

하지만 A 액티비티에서 B액티비티를 갔는데 A액티비티를 죽이고 싶다면 이렇게 하면된다.

우선 A 클래스에 

public static Activity AActivity;


라고 스테틱으로 액티비티를 선언한다. 그리고 onCreate 안에 

AActivity = Aclass.this;


이렇게, 액티비티 객체에 현재 클래스를 담아준다. 그럼 이제 B클래스로가서

Aclass aActivity = (Aclass)Aclass.AActivity;


이렇게 aActivity 라는 객체를 만드는데 그 객체는 Aclass의 스테틱변수인 AActivity를

넣어 둔다는 의미이다 보면 복잡해 보이는데 글로 쓰면 이렇다 .

A클래스  A클래스객체  =  (A클래스)A클래스.A클래스에서 스태틱 액티비티 변수


이정도 순서이다. 헷갈리기 쉬울 것 같다. 

그 다음 아래에 이렇게 만든 객체를 피니시하면된다

aActivity.finish();


이렇게 써주면 B클래스에서 아까 앞에 A액티비티를 종료 시킬 수 있다.

출처 : http://www.kmshack.kr/249

mainActivity.java

 

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);     
     
        intentMyService = new Intent(this,PersistentService.class);        // 죽지않는 좀비 서비스
        receiver = new RestartService();                                                    // 리시버 등록
       
  try{
   IntentFilter mainFilter = new IntentFilter("com.jongbum.ServiceTest.ssss");      // xml에서 정의해도 됨
   registerReceiver(receiver, mainFilter);                                               // 리시버 저장

   startService(intentMyService);                                                           // 서비스 시작 
  }
  catch (Exception e) {
   Log.d("RestartService",e.getMessage()+"");
  }
}
   
 @Override
 protected void onDestroy() {
  unregisterReceiver(receiver);                                                               //리시버 삭제(?) 안하면 에러!!
  super.onDestroy();
 }

 

PersistentService.java

 

public class PersistentService extends Service {
 
 @Override
 public void onCreate() {
  Log.d("PersistentService","onCreate");
  unregisterRestartAlarm();                                                 //이미 등록된 알람이 있으면 제거
  super.onCreate();
 }
 
 @Override
 public void onDestroy() {
  Log.d("PersistentService","onDestroy");
  registerRestartAlarm();                                                   // 서비스가 죽을때 알람을 등록
  super.onDestroy();

 }

 

 // support persistent of Service
 public void registerRestartAlarm() {
  Log.d("PersistentService", "registerRestartAlarm");
     Intent intent = new Intent(PersistentService.this, RestartService.class);
     intent.setAction("ACTION.RESTART.PersistentService");
     PendingIntent sender = PendingIntent.getBroadcast(PersistentService.this, 0, intent, 0);
     long firstTime = SystemClock.elapsedRealtime();
     firstTime += 10*1000;                                               // 10초 후에 알람이벤트 발생
     AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
     am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstTime, 10*1000, sender);
  }

  public void unregisterRestartAlarm() {
     Log.d("PersistentService", "unregisterRestartAlarm");
     Intent intent = new Intent(PersistentService.this, RestartService.class);
     intent.setAction("ACTION.RESTART.PersistentService");
     PendingIntent sender = PendingIntent.getBroadcast(PersistentService.this, 0, intent, 0);
     AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
     am.cancel(sender);
   }

 

 @Override
 public IBinder onBind(Intent intent) {
  Log.d("RestartService", "onBindonBindonBindonBindonBindonBindonBind");
  return null;
 }
 
}

 

RestartService .java

 

public class RestartService extends BroadcastReceiver {
   

   @Override
   public void onReceive(Context context, Intent intent) {
    Log.d("RestartService", "RestartService called! :" + intent.getAction());

   

    /* 서비스 죽일때 알람으로 다시 서비스 등록 */

     if(intent.getAction().equals("ACTION.RESTART.PersistentService")){
     Log.d("RestartService", "ACTION_RESTART_PERSISTENTSERVICE");
     Intent i = new Intent(context,PersistentService.class);
     context.startService(i);
    } 

    /* 폰 재부팅할때 서비스 등록 */
    if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
     Log.d("RestartService", "ACTION_BOOT_COMPLETED");
     Intent i = new Intent(context,PersistentService.class);
     context.startService(i);
    }
   }
}

 

 

Manifest.xml

  




출처 : https://github.com/johannilsson/android-pulltorefresh

 

Pull To Refresh for Android

This project aims to provide a reusable pull to refresh widget for Android.

Screenshot

Repository at https://github.com/johannilsson/android-pulltorefresh.

Usage

Layout

<!--
  The PullToRefreshListView replaces a standard ListView widget.
-->
<com.markupartist.android.widget.PullToRefreshListView
    android:id="@+id/android:list"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"
    />

Activity

// Set a listener to be invoked when the list should be refreshed.
((PullToRefreshListView) getListView()).setOnRefreshListener(new OnRefreshListener() {
    @Override
    public void onRefresh() {
        // Do work to refresh the list here.
        new GetDataTask().execute();
    }
});

private class GetDataTask extends AsyncTask<Void, Void, String[]> {
    ...
    @Override
    protected void onPostExecute(String[] result) {
        mListItems.addFirst("Added after refresh...");
        // Call onRefreshComplete when the list has been refreshed.
        ((PullToRefreshListView) getListView()).onRefreshComplete();
        super.onPostExecute(result);
    }
}

출처 : http://www.kmshack.kr/346

 

 

얼마전 ListView 포퍼먼스 팁에 관한 블로그 포스팅을 한적이 있다.  Adapter에서 View의 재활용과 함께 ViewHolder Pattern으로 findViewById를 View생성 시점에 setTag()를 하여 재활용에 대해 언급 했다. 


이 방법은 각 ListView의 ViewItem별로 각각의 ViewHolder를 가지고 있어야 한다.  

ListView의 아이템별로 서로 다른 디자인이 필요하기에 View의 종류가 달라 질 수 밖에 없기때문에 ViewHolder도 각각 존재 할 수 밖에 없다. 이렇게 static하게 ViewHolder을 가지고 있는것 보다 유연하게 ViewHolder를 생성 할 수 있는 코드를 생성하는 방법에 대해서 알아보자.


아래처럼 Adapter에 static class로 만들어 ViewHolder를 사용하는것이 일반적이다.  ViewHolder는 같은 기능을 하는데 단순히 View의 종류가 달라져 각각 생성 할수 밖에 없는 상황이다. 


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private static class ViewHolder {
 
    public final ImageView bananaView;
    public final TextView phoneView;
 
    public ViewHolder(ImageView bananaView, TextView phoneView) {
        this.bananaView = bananaView;
        this.phoneView = phoneView;
    }
}
 
@Override
public View getView(int position, View convertView, ViewGroup parent) {
 
    ImageView bananaView;
    TextView phoneView;
 
    if (convertView == null) {
        convertView = LayoutInflater.from(context).inflate(R.layout.banana_phone, parent, false);
        bananaView = (ImageView) convertView.findViewById(R.id.banana);
        phoneView = (TextView) convertView.findViewById(R.id.phone);
        convertView.setTag(new ViewHolder(bananaView, phoneView));
    } else {
        ViewHolder viewHolder = (ViewHolder) convertView.getTag();
        bananaView = viewHolder.bananaView;
        phoneView = viewHolder.phoneView;
    }
 
    BananaPhone bananaPhone = getItem(position);
    phoneView.setText(bananaPhone.getPhone());
    bananaView.setImageResource(bananaPhone.getBanana());
 
    return convertView;
}

 



해결책으로 아래와 같이 ViewGroup의 View들에 대해 동적으로 생성하는 코드를 만들 수 있다.


 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ViewHolder {
    @SuppressWarnings("unchecked")
    public static <T extends View> T get(View view, int id) {
        SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
        if (viewHolder == null) {
            viewHolder = new SparseArray<View>();
            view.setTag(viewHolder);
        }
        View childView = viewHolder.get(id);
        if (childView == null) {
            childView = view.findViewById(id);
            viewHolder.put(id, childView);
        }
        return (T) childView;
    }
}

 



모든 View들은 View를 상속 받고 있기 때문에  SparseArray를 통해서  ChildView의 Id가 없으면 findViewById()를 통해 put해주고 있으면 get으로 가져오는 방식이다. 


 

사용하는 간단한 예를 알아보면:

 


 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public View getView(int position, View convertView, ViewGroup parent) {
 
    if (convertView == null) {
        convertView = LayoutInflater.from(context).inflate(R.layout.banana_phone, parent, false);
    }
 
    ImageView bananaView = ViewHolder.get(convertView, R.id.banana);
    TextView phoneView = ViewHolder.get(convertView, R.id.phone);
 
    BananaPhone bananaPhone = getItem(position);
    phoneView.setText(bananaPhone.getPhone());
    bananaView.setImageResource(bananaPhone.getBanana());
 
    return convertView;
}


 

기존의 방법보다 더 간결화되고 심플해진 것을 볼 수 있다.


출처 :  http://www.techrepublic.com/blog/software-engineer/create-a-transparent-progress-dialog-on-android/

 

A game development hobby project inspired this app developer to figure out a way to create a transparent progress dialog that animates an image. He describes how to do it.

 

Over the last year I've been working hard in my spare time writing a multiplayer game for mobile devices. It's slow going, and often after putting in eight or more hours at my day gig (where I also write applications), I start thinking I need to get a new hobby that doesn't involve computers, cell phones, or tablets.

Still, I do it not just because I enjoy seeing my games out there in the market, but also because game programming is unique in that it often tends to veer from the standard platform conventions. Game developers frequently find themselves thinking creatively, particularly when it comes to user experience and user interface. 

What's all this got to do with transparent progress dialogs on Android? Well, Android's out-of-the-box progress dialog is anything but exciting (Figure A).

Figure A

 

AndroidProcessDialog_FigA_082713.png

 

See what I mean?

 

For my game I needed the functionality of a blocking progress dialog without the dialog part. After poking around the Android documentation, a number of developer forums, and a lot of trial and error, I succeeded in creating a transparent progress dialog that simply animates an image of my choosing. This tutorial walks you through the steps, so you can do the same. If you prefer, you can download and import the project directly in Eclipse.

1. Create a new Android project in Eclipse targeting Android 2.2 or better.

2. In the /res/layout folder, create a linear layout in activity_main.xml. Add a text label and a button.

 

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:padding="8dp"
        android:textSize="20sp"
        android:text="Transparent Progress Indicator" />
    
    <Button 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Check it out!"
        android:layout_marginTop="40dp"
        android:layout_gravity="center"
        android:id="@+id/the_button" />

</LinearLayout>

 

3. In the /res/values folder, open styles.xml, which is where you will add the style for our transparent dialog. Be sure to specify the parent attribute, or you will have problems at runtime.

 

styles.xml
<resources>

    <!--
        Base application theme, dependent on API level. This theme is replaced
        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
    -->
    <style name="AppBaseTheme" parent="android:Theme.Light">
        <!--
            Theme customizations available in newer API levels can go in
            res/values-vXX/styles.xml, while customizations related to
            backward-compatibility can go here.
        -->
    </style>

    <!-- Application theme. -->
    <style name="AppTheme" parent="AppBaseTheme">
        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
    </style>
    
    <!--  Transparent dialog -->
    <style name="TransparentProgressDialog" parent="@android:Theme.Dialog">
        <item name="android:windowFrame">@null</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowTitleStyle">@null</item>
        <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
        <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
        <item name="android:backgroundDimEnabled">true</item>
        <item name="android:background">@android:color/transparent</item>
    </style>

</resources>

 

4. Create a /drawable folder in the /res directory and add an image to represent our spinner. The animation is a simple rotation, so you'll want to use something that is exactly as wide as it is tall. Here's the image I chose (Figure B).

Figure B

 

AndroidProcessDialog_FigB_082713.png

 

5. Now you can implement your MainActivity.java file. First, you override the on create and wire up your button handler. Note that in the on create you are instantiating a dialog and a handler; these reference an inner class that you will get to shortly. The last thing MainActivity.java is responsible for is releasing the handler and the dialog when the activity gets destroyed.

 

MainActivity.java
package com.authorwjf.transparentprogressdialog;

import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class MainActivity extends Activity implements OnClickListener {

	private TransparentProgressDialog pd;
	private Handler h;
	private Runnable r;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		h = new Handler();
		pd = new TransparentProgressDialog(this, R.drawable.spinner);
		r =new Runnable() {
			@Override
			public void run() {
				if (pd.isShowing()) {
					pd.dismiss();
				}
			}
		};
		findViewById(R.id.the_button).setOnClickListener(this);
	}
	
	@Override
	public void onClick(View v) {
		pd.show();
		h.postDelayed(r,5000);
	}
	
	@Override
	protected void onDestroy() {
		h.removeCallbacks(r);
		if (pd.isShowing() ) {
			pd.dismiss();
		}
		super.onDestroy();
	}

}

 

6. Add the inner class just below your on destroy method. The constructor builds your transparent dialog, and the show method displays it and starts the animation running.

 

private class TransparentProgressDialog extends Dialog {
		
	private ImageView iv;
		
	public TransparentProgressDialog(Context context, int resourceIdOfImage) {
		super(context, R.style.TransparentProgressDialog);
        	WindowManager.LayoutParams wlmp = getWindow().getAttributes();
        	wlmp.gravity = Gravity.CENTER_HORIZONTAL;
        	getWindow().setAttributes(wlmp);
		setTitle(null);
		setCancelable(false);
		setOnCancelListener(null);
		LinearLayout layout = new LinearLayout(context);
		layout.setOrientation(LinearLayout.VERTICAL);
		LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
		iv = new ImageView(context);
		iv.setImageResource(resourceIdOfImage);
		layout.addView(iv, params);
		addContentView(layout, params);
	}
		
	@Override
	public void show() {
		super.show();
		RotateAnimation anim = new RotateAnimation(0.0f, 360.0f , Animation.RELATIVE_TO_SELF, .5f, Animation.RELATIVE_TO_SELF, .5f);
		anim.setInterpolator(new LinearInterpolator());
		anim.setRepeatCount(Animation.INFINITE);
		anim.setDuration(3000);
		iv.setAnimation(anim);
		iv.startAnimation(anim);
	}
}

 

Load the resulting application to a device or the emulator to have a look (Figure C).

Figure C

 

AndroidProcessDialog_FigC_082713.png

 

It's not every day you want a big red sunburst in the middle of your application UI, but the point is you can put any image of any size there. You can apply any of Android's built-in animations, and adding floating text is as easy as instantiating a text view in the class constructor and pushing it into the layout.

Once you have a chance to play around with it, share your thoughts in the discussion thread.

 

 

About William J. Francis

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

[안드로이드] Event 처리 메커니즘

출처 : http://ecogeo.tistory.com/251 

안드로이드의 이벤트 처리 과정에 대한 글(http://blog.naver.com/osk1004?Redirect=Log&logNo=50069078782 )을 참조하여 나름대로 분석하여 메모한 결과를 적어본다.

개략적인 이벤트 처리 과정
  1. 액티비티 생성시 액티비티의 윈도우를 WindowManagerService에 등록해둠
  2. 이벤트 발생시 네이티브 라이브러리(EventHub)를 통해 이벤트 읽음
  3. 이벤트 큐(KeyInputQueue)에 이벤트 쌓임
  4. 이벤트 디스패치 쓰레드(InputDispatcherThread)는 이벤트큐에서 이벤트를 꺼내어
    WindowManagerService의 디스패치 메소드 호출
  5. WindowManagerService는 등록된 애플리케이션의 윈도우에 이벤트를 전달
  6. 이벤트를 전달받은 윈도우는 하위 UI 컴포넌트 트리를 찾아가며 리스너 콜백함수 실행


이벤트 전달을 위한 준비

WindowManagerService는 system_server 프로세스에서 실행중인 서비스이다. WindowManagerService에서 감지된 이벤트를 애플리케이션 프로세스의 UI 컴포넌트에 전달하기 위해서 둘 사이에 연결고리가 미리 만들어져 있어야 한다. 통신은 AIDL을 통해서 이루어진다.

애플리케이션의 윈도우를 WindowManagerService에 등록하는 과정

  1. ActivityManagerService는 ActivityThread를 호출하여 액티비티 런치.
    ActivityThread.performLaunchActivity()에서 Activity인스턴스 생성하고 activity.attach() 호출
  2. Activity는 attach()에서 PhoneWindow 객체 생성. 이 PhoneWindow는 액티비티내 뷰들의 root로서 DecorView 인스턴스 포함.

    mWindow = PolicyManager.makeNewWindow(this);
     
  3. ActivityManagerService는 ActivityThread를 호출하여 액티비티를 resume시킴.
    WindowManager 인스턴스가 생성되고 decorView가 WindowManager에 추가됨.

    ActivityThread.handleResumeActivity()
     
  4. WindowManager의 addView(decor)에서 ViewRoot 인스턴스를 생성하고 viewRoot.setView(decor) 호출
  5. viewroot.setView(decor)에서 IWindowSession을 통해 WindowManagerService에 IWindow인스턴스를 추가

    IWindowSession.add(window) 
     

DecorView 클래스
- FrameLayout을 상속받으며, PhoneWindow의 내부 클래스로 정의됨
- 표준 윈도우 프레임 및 데코레이션을 포함하는 최상위 윈도우 뷰
- 윈도우 매니저에 윈도우로서 추가됨

ViewRoot 클래스
- WindowManager와 View 사이의 protocol을 위한 구현 포함
- Handler를 상속받음
- IWindow 서비스 구현 클래스(W)를 내부 클래스로 포함 :  class W extends IWindow.Stub

관련 AIDL
IWindowSession.aidl : 애플리케이션 --> WindowManagerService

    int add(IWindow window, ... ...); // 윈도우를 WindowManagerService에 추가
    void remove(IWindow window);   

IWindow.aidl : WindowManagerService --> 애플리케이션

    void dispatchKey(in KeyEvent event); //이벤트를 애플리케이션에 전달
    void dispatchPointer(in MotionEvent event, ...);
    void dispatchTrackball(in MotionEvent event, ...);

KeyEvent.aidl, MotionEvent.aidl : 프로세스간 전달되는 이벤트 정보


이벤트 감지 및 디스패치
이벤트를 검출하고 애플리케이션으로 디스패치하는 로직은 WindowManagerService.java에 구현되어 있다. WindowManagerService는 InputDispatcherThread와 KeyInputQueue 구현 클래스를 이용하여 이벤트를 읽어들이고 적절한 윈도우에 전달하는 일을 한다.

WindowManagerService 클래스
- KeyInputQueue 구현 클래스(KeyQ) 및 InputDispatcherThread 클래스 포함
- WindowManagerService 인스턴스 생성시 KeyInputQueue 생성 및 InputDispatcherThread 쓰레드 시작
- InputDispatcherThread는 이벤트 타입에 따라 WindowManagerService의 디스패치 메소드 호출.

       dispatchKey(KeyEvent); // 예를들어 키보드 이벤트인 경우

- 디스패치 메소드는 현재 포커스를 가진 윈도우를 찾아 이벤트 전달

     mKeyWaiter.waitForNextEventTarget(); // WindowState 찾음
     windowState.mClient.dispatchKey(event); // windowState.mClient는 IWindow 객체

- IWindow 객체는 액티비티가 resume인 상태가 되면서 ViewRoot가 WindowServiceManager에 전달한 것

KeyInputQueue 클래스
- 안드로이드에서 진짜 이벤트 큐 역할
- 인스턴스 생성시 새로운 쓰레드가 시작되면서 native boolean readEvent() 메소드를 무한루프 호출.
- 리눅스 입력 디바이스로부터 실제 이벤트를 읽어들이는 로직은 네이티브 코드로 구현됨 : EventHub
- 이벤트 읽는 과정 

   KeyInputQueue.java -> JNI -> com_android_server_KeyInputQueue.cpp -> EventHub.cpp -> Device

InputDispatcherThread 클래스
- Event-Dispatch Thread 구현 클래스(?)
- 무한루프 돌면서 이벤트 큐에서 이벤트를 꺼내 WindowManagerService의 디스패치 메소드 호출

이벤트 유형
- 키보드 : RawInputEvent.CLASS_KEYBOARD
- 트랙볼 : RawInputEvent.CLASS_TRACKBALL
- 터치스크린 : RawInputEvent.CLASS_TOUCHSCREEN
- 설정 변경 : RawInputEvent.CLASS_CONFIGURATION_CHANGED

이벤트 정보를 담고 있는 핵심 클래스
- 키보드 이벤트 : KeyEvent
- 터치 or 트랙볼 이벤트 : MotionEvent


애플리케이션에서 이벤트 처리 과정(키 이벤트 중심)

이벤트를 전달받은 애플리케이션 윈도우는, 뷰 트리의 최상위부터 시작해서 실제 포커스를 가진 뷰까지 경로를 따라 이벤트를 디스패치한다.

  1. 이벤트가 발생하면 WindowManagerService는 이벤트 큐의 이벤트를 IWindow에 전달

    IWindow.dispatchKey(event);
     
  2. IWindow(ViewRoot.W 내부클래스가 구현)는 이벤트를 ViewRoot의 dispatchKey(event)에 다시 전달
  3. ViewRoot.dispatchKey()에서는 sendMessageAtTime(msg) 메소드를 통해 메시지 형태로 이벤트를 전달(왜 갑자기 여기서 Handler 메시지 형태로 이벤트를 전달하는가?)
  4. 보내진 이벤트 메시지는 Handler를 상속받은 ViewRoot의 handleMessage(Message)가 처리
  5. handleMessage()는 deliverKeyEventToViewHierarchy(event)를 호출
  6. deliverKeyEventToViewHierarchy()는 decor view의 dispatchKeyEvent(event) 호출

    mView.dispatchKeyEvent(event);
     
  7. decor view의 dispatchKeyEvent()에서는 현재 뷰에 리스너가 등록되어 있으면 현재 view의 리스너 콜백함수를 호출함(즉 드디어 이벤트가 처리됨)
  8. 등록된 리스너가 없으면 KeyEvent의 dispatch(callback) 호출 : callback은 view 자신
  9. KeyEvent.dispatch()는 다시 callback view의 onKeyDown() 호출 : 키 누름 이벤트인 경우
  10. view의 onKeyDown()은 setPressed() 호출 : setPressed() ->dispatchSetPressed()
  11. dispatchSetPressed()는 하위 View 클래스(예를들어 ViewGroup)에서 적절히 오버라이드됨
  12. ViewGroup의 dispatchSetPressed()에서는 자식 뷰들의 setPressed()를 호출
  13. 이런식으로 최종 타겟 UI 컴포넌트까지 이벤트가 디스패치됨


View 클래스
- KeyEvent.Callback 구현
- 주요 디스패치 메소드 :

    dispatchKeyEvent(KeyEvent event);
    dispatchTouchEvent(MotionEvent event);
    dispatchTrackballEvent(MotionEvent event);

ViewGroup 클래스
- XXXLayout 들의 부모 클래스
- dispatchSetPressed(boolean pressed) 메소드 코드 :

        final View[] children = mChildren;
        final int count = mChildrenCount;
        for (int i = 0; i < count; i++) {
            children[i].setPressed(pressed);
        }

출처 : http://www.coremodeling.com/android/tutorial/AppWidget/AppWidget.html

 

App Widget

  1. 작성자 : 고덕한(deokhan.koh@gmail.com)
  2. 소속 : 코아모델링(www.coremodeling.com)
  3. 작성일자 : 2011년 9월 22일

Android 홈페이지에 있는 App Widget 문서를 번역하면서 AppWidget 에 대한 개념과 개발하는 방법을 익히도록 합니다.

URL 은 http://developer.android.com/guide/topics/appwidgets/index.html 클릭하면 됩니다.

App Widget 은 Home Screen 에 보여지는 것과 같이 다른 Application 에 Embedded 될 수 있고 주기적으로 update 가 되는 미니어처 어플리케이션 View 라 할 수 있다. 이러한 View 들은 User Interface 에서 Widget 이라 일컬어진다. 그리고 App Widget Provider 를 사용하여 개발하여 설치가능하다. App Widget 을 포함한 어플리케이션 컴포넌트를 App Widget host 라 일컫는다. App Widget 은 아래와 같은 Music App Widget 같은 것을 말한다.

1. The Basics

App Widget 을 개발하기 위해서는 아래와 같이 따른다.

1.1 AppWidgetProviderInfo 객체

AppWidget 에 대한 metadata 를 표현하는 것으로써, App Widget 의 layout, update 주기, 그리고 AppWidgetProvider 클래스 등의 정보를 표시한다. 이러한 내용을 XML 파일에 정의해야만 한다.

위치는 /res/xml 디렉토리에 파일을 위치한다.

1.2 AppWidgetProvider 클래스구현

AppWidget 이 동작하기 위해서 AppWidgetProvider 를 상속받아서 구현해야 한다. AppWidget 으로써 동작하기 위해서 필요한 메소드를 Override 해야하며, 이는 Broadcast 이벤트를 수신하여 필요한 기능이 동작하기 위함이다. updated, enabled, disabled, 그리고 deleted 메소드를 Override 한다.

1.3 View layout

Layout XML 파일이며, Widget 이 초기에 보여기 위한 layout 파일이다.

추가적으로 App Widget 의 환경설정을 위한 Configuration Activity 를 추가할 수 있다. 이것은 옵션 사항으로써 AppWidget 에 처음 생성될 때 사용자가 AppWidget 에 환경을 설정하도록 지원하는 기능이다.

2. Declaring an App Widget in the Manifest

첫번째로 AppWidgetProvider 클래스를 AndroidManifest.xml 파일에 등록한다. 예제는 아래와 같다.

<receiver android:name="ExampleAppWidgetProvider" >

    <intent-filter>

        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

    </intent-filter>

    <meta-data android:name="android.appwidget.provider"

               android:resource="@xml/example_appwidget_info" />

</receiver>

AppWidgetProvider 는 BroadcastReceiver 를 상속받아서 작성된 클래스이다. 따라서 AndroidManifest.xml 에 등록하려면, <receiver> 태그를 사용하여 등록을 한다.

위 예제에서 보듯이 <intent-filter> 안에 <action> element를 android.appwidget.action.APPWIDGET_UPDATE 를 추가한다. 이는 이 APPWIDGET_UPDATE action 에 대한 broadcast message 가 발생하면, 이 AppWigetProvider 가 동작하게끔 처리하는 것이다. AppWidgetManager 는 다른 모든 AppWidget 에게 broadcast message 를 필요한 경우에 자동으로 전송을 한다.

<meta-data> element 를 AppWidgetProviderInfo 에 해당하는 Resource 의 xml 파일을 지정한다.

3. Adding the AppWidgetProviderInfo Metadata

AppWidgetProviderInfo 는 xml 파일로 정의하며, App Widget 을 작성하기 위해서 반드시 필요한 파일이다. 이 XML 파일에는 initial layout resource, App Widget 의 upate 주기, 그리고 옵션으로 처음 생성 시점에 실행되는 configuration Activity 정보를 설정한다. XML 파일에 <appwidget-provider> element 로 정의하며, XML 파일은 /res/xml 디렉토리에 위치한다.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

    android:minWidth="294dp"

    android:minHeight="72dp"

    android:updatePeriodMillis="86400000"

    android:previewImage="@drawable/preview" <!-- android 3.0 이후에 추가 -->

    android:initialLayout="@layout/example_appwidget"

    android:configure="com.example.android.ExampleAppWidgetConfigure"

    android:resizeMode="horizontal|vertical"> <!-- android 3.1 이후에 추가 -->

</appwidget-provider>

다음은 각 attribute 에 대한 설명이다.

3.1 minWidth 와 minHeight

AppWidget layout 의 최소 폭과 높이의 값을 설정한다.

Home Screen 에 보여질 Widget 에 대한 높이와 폭을 지정하는 것이다. Home Screen 은 아이콘 크기의 Cell 형태로 이루어지져 있다. 따라서 Widget 의 폭과 높이를 지정할 때, HomeScreen 에 놓여질 Cell 의 크기를 기준으로 계산을 해야한다. 기본적인 Cell 의 크기는 74 dp 이며, 그 값을 계산하는 방식은 아래와 같다.

(number of cell * 74)  - 2

주의 : Home Screen 에서 Widget 이 가질 수 있는 최대의 Cell 의 갯수는 4 x 4 이다, 이보다 크게 Widget 의 크기를 디자인해서는 안된다.

3.2 updatePeriodMillis

AppWidget Framework 가 AppWidgeProvider.onUpdate() 메소드를 얼마나 자주 호출해야 하는지를 정의한다. 하지만 실제로 onUpdate() 가 정확한 시간에 호출되는지는 보증할 수 없다. 가능한한 제 시간에 호출되는 것으로 가정해야 하며, 어떨 때는 Battery 문제로 인하여  한시간에 한번도 호출되지 않을 수도 있다. 주로 사용자가 15분 주기 또는 1시간 주기등으로 설정한다.

주의 : updatePeriodMillis 를 설정해 놓으면 Device 가 sleep mode(주로 화면이 꺼진 상태) 로 되어있을 지라도 update 를 수행하기 위해서 Device 를 깨워서 동작을 한다. 이러한 작업이 1시간 주기로 동작하면, Battery 문제가 없지만, 자주 호출이 되면 Battery 를 많이 소모하게 된다. 따라서 Device 가 꺠어있는 상태에서만 동작하도록 하려면 updatePeriodMillis 의 값은 “0” 으로 설정하고, AlarmManager 를 사용하여 주기적으로 동작하도록 설정해야 한다. 이때 Alarm 의 Type 은 ELASPED_REALTIME 또는 RTC  로 설정하면 된다.

3.3 initialLayout

AppWidget layout 을 정의하기 위한 layout resource 를 지정한다.

3.4 configure

사용자가 App Widget 을 추가할 때 실행할 Activity 를 지정한다. 이 Activity 는 App Widget 의 환경설정을 하는데 주로 사용한다.

3.5 previewImage

App Widget 을 추가하기 위해서 Menu 를 선택하고 App Widget 목록에서 해당 Widget 에 대한 미리보기 이미지를 지정한다. 이 항목이 지정되지 않았을 경우에는 디폴트 Icon 으로 나타난다. 이 속성은 Android 3.0 부터 지원한다.

3.6 autoAdvanceViewId

해당 Widget host 에 의해서 auto-advanced 되어야 하는 App Widget subview 의 view ID 를 기술한다. 이 속성은 Android 3.0 부터 지원한다.

3.7 resizeMode

Home Screen 에서 Widget 의 폭과 높이에 대해서 Resize 할 것인지를 정의한다. 값은 “vertical”, “horizontal”, “none” 으로 설정한다. 이 속성은 Android 3.1 부터 지원한다.

4. Creating the App Widget Layout

Widget 이 처음 실행되었을 때, Home Screen 보여질 Layout 을 설정해야 한다. 우선 /res/layout 디렉토리에 Layout 을 생성해야 한다.

Layout 을 생성하는 것은 기존의 Layout 을 작성하는 것과 별반 차이가 없으나, Widget 에서는 사용할 수 있는 View 가 한정되어 있다.

Home Screen 에 보여지는 View 는 RemoteViews 에 기반하고 있으며, RemoteViews 하위에 디자인한 View 가 속하게 된다.

기존의 모든 layout class 들을 모두 지원하지는 않고, 아래의 클래스들을 지원한다.

FrameLayout, LinearLayout, Relativelayout

AnalogClock, Button, Chronometer, ImageButton, ImageView, ProgressBar, TextView, ViewFlipper

5. Using the AppWidgetProvider Class

AppWidgetProvider 클래스는 BroadcastReceiver 클래스를 상속받는다. 이로 인해서 App Widget broadcast message 손쉽게 처리할 수 있다. AppWidgetProvier 클래스는 AppWidget 에 관련된 broadcast message 만 수신한다. 이 메시지는 updated, deleted, enabled, 그리고 disabled 이다. 이 메시지를 받을 때마다 이에 관련된 다음 메소드들이 호출된다.

5.1 onUpdate()

이 메소드는 AppWidgetProviderInfo 로 지정된 XML 파일에서 updateMillis 속성에 정의된 주기로 호출된다. 또한 이메소드는 사용자가 Home Screen 에서 App Widget 을 추가했을 때 호출된다. 이때 View 에 이벤트 핸들러를 추가하거나 임시 Service 를 호출하는 등의 필요한 초기 설정 작업을 수행해야 한다.

하지만 configuration Activity 가 정의된 경우에는 사용자가 Home Screen 에 App Widget 을 추가할 때, 이 메소드는 호출되지 않는다. 단지 주기적으로 호출될 때 실행된다. 따라서 Activity 를 지정하였을 경우에는, Configuration 을 완성하기 위해서 Activity 에서 onUpdate() 메소드를 처음에 호출하도록 구현해야 한다.

5.2 onDeleted(Context, int[])

App Widget host 에서 삭제될 때 호출된다.

5.3 onEnabled(Context)

App Widget 이 처음으로 instance 가 생성될 때 호출된다. 예를 들어 사용자가 두 개의 App Widget instance 를 추가한다 하더라도, 처음에 한번만 호출이 된다. 이 메소드에 초기에 필요한 작업들을 구현한다. 주로 데이터베이스 설정같은 작업을 구현한다.

5.4 onDisabled(Context)

App Widget host 에서 마지막 App Widget instnace 가 삭제될 때 호출된다. 이 메소드에서는 onEnabled() 에서 초기화된 작업을 모두 제거하는 기능을 주로 구현한다.

onDeleted() 는 여러개의 Instance 중에서 각각을 삭제할 때 호출되지만, onDisabled() 는 마지막 Instance 가 삭제될 때 호출된다.

5.5 onReceive(Context, Intent)

위의 메소드가 호출되기 이전에 먼저 호출된다. 일반적으로 이 메소드를 구현할 필요는 없으며, 공통적으로 처리할 내용이 있을 경우에 구현한다.

AppWidgetProvider 에서 가장 중요한 메소드가 onUpdate() 이다. 왜냐하면 이 메소드는 사용자가각각의  App Widget 을 host 에 추가할 때 호출되기 때문이다. 만약 App Widget 이 사용자의 Event 를 수용한다면, 이 메소드에서 Event handler 를 등록해야 한다. 아래 코드는 onUpdate() 메소드에서 Click 이벤트를 등록하는 코드이다.

public class ExampleAppWidgetProvider extends AppWidgetProvider {

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {

        final int N = appWidgetIds.length;

        // 이 Provider 에 속하는 각각의 App Widget 에 대해서 순차적으로 Loop 을 수행

        for (int i=0; i<N; i++) {

            int appWidgetId = appWidgetIds[i];

            // ExampleActivity 를 실행할 Intent 를 생성

            Intent intent = new Intent(context, ExampleActivity.class);

            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

            // 버튼에 onClick listener 를 설정하기 위해서 App Widget 에 대한 layout 의 참조를 얻는다.

            RemoteViews views = new RemoteViews(context.getPackageName(), 

                                                                                  R.layout.appwidget_provider_layout);

            views.setOnClickPendingIntent(R.id.button, pendingIntent);

            // AppWidgetManager 이 현재 App Widget 에 update 를 수행하도록 처리한다.

            appWidgetManager.updateAppWidget(appWidgetId, views);

        }

    }

}

위 소스의 내용을 설명하자면, Activity 를 실행하기 위해서 PendingIntent 를 사용한다. 또한 버튼에 클릭이벤트를 OnClickPendingIntent 로 지정한다. 그리고 하나의 App Widget 에 여러개의 Instance 를 생성했을 경우( Home Screen 에 똑같은 Widget 을 두개 이상 추가했을 경우) 에 모든 Widget 에 동일한 효과를 주기 위해서 Loop 을 사용하여 설정하였다.

주의 : AppWidgetProvider 는 BroadcastReceiver 클래스를 상속받았기에, 시간이 많이 소요되는 작업은 그 처리 결과를 보증할 수 없다. 따라서 시간이 많이 걸리는 작업은 별도의 Service 를 만들어서 실행해야 한다.

5. 7 Receiving App Widget broadcast Intents

AppWidgetProvider 클래스는 BroadcastReceiver 클래스를 상속받아서 편리하게 사용하기위해 작성된 클래스이다. 따라서 BroadcasetRecevier 로 구현하듯이 작성한 후에, 아래의 Intent 를 사용하면 된다.

  1. ACTION_APPWIDGET_UPDATE
  2. ACTION_APPWIDGET_DELETED
  3. ACTION_APPWIDGET_ENABLED
  4. ACTION_APPWIDGET_DISABLED

6. Creating an App Widget Configuration Activity

사용자가 App Widget 을 처음 만들고 나서, App Widget 에 대한 환경설정을 하기 위해서는 App Widget Configuration Activity 를 생성할 수 있다. 이 Activity 는 App Widget host 에 의해서 자동으로 실행되며, 사용자가 App Widget 을 처음 설치할 때 설정을 할 수 있도록 하는 것이다. 이 Activity 로 App Widget 에 대한 색, 크기, 업데이트 주기 또는 다른 설정들을 할 수 있다.

Configuration Activity 는 일반적인 다른 Activity 처럼 AndroidManifest.xml 에 등록이 되어야 하며, “ACTION_APPWIDGET_CONFIGURE” action 으로 실행되어진다. 아래 예제와 같이 작성하면 된다.

<activity android:name=".ExampleAppWidgetConfigure">

    <intent-filter>

        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>

    </intent-filter>

</activity>

또한 이 Activity 는 AppWidgetProviderInfo XML 파일에 android:configure 항목에 정의가 되어야 한다. 아래와 같이 작성한다.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

    ...

    android:configure="com.example.android.ExampleAppWidgetConfigure"

    ... >

</appwidget-provider>

주의 : Activity 를 정의할 때, fully-qualified namespace 를 사용하여 정의해야 한다.

지금까지 Activity 를 설정하는 것을 살펴보았으며, 설정은 이것으로 간단하게 처리할 수 있다. 하지만 Activity 내에서는 두가지 중요한 기능을 구현해야 한다.

  1. App Widget host 에서 Configuration Activity 를 실행할 때, Activity 는 result 를 리턴 받는다. result 는 Intent 로 받으며, 이 Intent 에 App Widget ID 값이 저장되어서 리턴받는다. Intent 에 EXTRA_APPWIDGET_ID 값으로 저장되어 있다.
  2. App Widget 이 생성될 때, onUpdate() 메소드가 호출되지 않는다. Configuration Activity 가 실행될 때 시스템에서 ACTION_APPWIDGET_UPDATE broadcast message 를 전송하지 않는다. 이것은 App Widget 이 처음으로 생성될 때, Configuration Activity가 AppWidgetManager 에서 update 를 요청해야하는 책임이 있다는 것이다. 즉, onUpdate() 메솓느는 처음에는 호출되지 않고, 지속적인 update 에서 호출된다.

6.1 Updating the App Widget from the configuration Activity

App Widget 이 configuration activity 를 사용할 때, 설정이 완료되어질 때, Activity 에서는 App Widget 이 update 되는 책임이 있다. AppWidgetManager 에서 직접 update 를 요청할 수 있다.

다음은 적절히 App Widget 을 update 하는 절차와 Activity 완료하는 절차를 요약했다.

6.1.1 첫번쨰, 실행되어진 Activity 에서 Intent 로부터 App Widget ID 를 얻는다.

Intent intent = getIntent();

Bundle extras = intent.getExtras();

if (extras != null) {

    mAppWidgetId = extras.getInt(

            AppWidgetManager.EXTRA_APPWIDGET_ID,

            AppWidgetManager.INVALID_APPWIDGET_ID);

}

6.1.2 App Widget 환경설정을 수행한다.
6.1.3 환경설정이 완료되었을 때, getInstance(Context) 메소드를 호출하여 AppWidgetManager 의 객체를 얻는다.

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

6.1.4 updateAppWidget(int, RemoteViews) 메소드를 호출하여 RemoteViews layout 을 사용하여 App Widget 화면을 업데이트 한다.

RemoteViews views = new RemoteViews(context.getPackageName(),

R.layout.example_appwidget);

appWidgetManager.updateAppWidget(mAppWidgetId, views);

6.1.5 마지막으로 리턴할 Intent 를 생성하고, Activity result 에 설정하고, Activity 를 종료한다.

Intent resultValue = new Intent();

resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);

setResult(RESULT_OK, resultValue);

finish();

팁 : configuration Activity 가 처음 실행될 때, Activity result 를 RESULT_CANCELED 로 설정한다. 이는 사용자가 최종적으로 완료되기 전에 취소할 경우 App Widget host 에서 환경설정이 최소되었다는 것을 알아야 하고, App Widget 이 추가되지 않는다.

7. Setting a Preview Image

Android 3.0 에서 previewImage 속성을 AppWidgetProviderInfo XMl 에 추가하였다. 이 항목은 Home Screen 에서 Widget 을 추가할 때, Widget 을 선택할 수 있는 목록에서 보여질 Preview 내용을 나타내는 것이다. 다음과 같이 XML 에 설정을 한다.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

  ...

  android:previewImage="@drawable/preview">

</appwidget-provider>

8. Using App Widgets with Collections

Android 3.0 에서는 Collection 을 포함한 App Widget 을 새로 추가했다. 이 AppWidget 은 백그라운드에 있는 Collection으로 된 Remote Data 를보여주기 위해서 RemoteViewsService 를 사용한다. 이 collection data 는 아래의 collection view 에 의해서 보여진다.

  1. ListView : Vertically scrolling list 를 보여주는 View
  2. GridView : two-dimensional scrolling grid 형태로 item 을 보여주는 View
  3. StackView : 마치 카드처럼 아래/위로 밀어서 내용을 보여주는 View
  4. AdapterViewFlipper : Adapter 기반으로 되어 있으며, 여러개의 View 를 애니매이션처리하는 ViewAnimator

위에서 언급했듯이, Remote data 를 Collection view 에 보여지기 위해서는 Adapter 를 사용해서 UI 에 bind 시켜야 한다. Adapter 각각의 Item 들을 개별적인 View 에 bind 시켜야 한다. Android Framework 에서는 app widget 에 이 데이터들을 bind 시켜야 하며, Adapter 가 이러한 역할을 수행해야 할 것을 RemoteViewFactory 가 대신 하고 있다.

Collection 에서 특정 item 이 요청될 때, 이 RemoteViewFactory 를 생성하고 해당 Item 에 대해서 RemoteViews 객체를 리턴한다. app widget 에 collection view 를 포함하기 위해서는 반드시 RemoteViewsService 와 RemoteViewsFactory 를 구현해야 한다.

RemoteViewsService 는 Service로써 RemoteViews 객체를 요청하는 원격 adapter 역할을 수행한다. RemoteViewsFactory 는 collection view 와 view 에 보여질 데이터 사이의 adpater 를 위한 interface 이다.

아래 예제코드를 참고한다.

public class StackWidgetService extends RemoteViewsService {

    @Override

    public RemoteViewsFactory onGetViewFactory(Intent intent) {

        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);

    }

}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

//... include adapter-like methods here. See the StackView Widget sample.

}

http://blog.naver.com/PostView.nhn?blogId=huewu&logNo=110099015747&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView

http://blog.daum.net/hopefullife/226

- Eclipse 자주 쓰는 단축키 -

- Eclipse 자주 쓰는 단축키 -

 

----- 실행 -----

Ctrl + F11 : 바로 전에 실행했던 클래스 실행

 

----- 소스 네비게이션 -----

Ctrl + 마우스커서(혹은 F3) : 클래스나 메소드 혹은 멤버를 상세하게 검색하고자 할때

Alt + Left, Alt + Right : 이후, 이전

Ctrl + O : 해당 소스의 메소드 리스트를 확인하려 할때

F4 : 클래스명을 선택하고 누르면 해당 클래스의 Hierarchy 를 볼 수 있다.


Alt + <-(->) : 이전(다음) 작업 화면

 

----- 문자열 찾기 -----

Ctrl + K : 찾고자 하는 문자열을 블럭으로 설정한 후 키를 누른다.

Ctrl + Shift + K : 역으로 찾고자 하는 문자열을 찾아감.

Ctrl + J : 입력하면서 찾을 수 있음.

Ctrl + Shift + J : 입력하면서 거꾸로 찾아갈 수 있음.

Ctrl + F : 기본적으로 찾기

 

----- 소스 편집 -----

Ctrl + Space : 입력 보조장치(Content Assistance) 강제 호출 => 입력하는 도중엔 언제라도 강제 호출 가능하다.

F2 : 컴파일 에러의 빨간줄에 커서를 갖져다가 이 키를 누르면 에러의 원인에 대한 힌트를 제공한다.

Ctrl + L : 원하는 소스 라인으로 이동

   로컬 히스토리 기능을 이용하면 이전에 편집했던 내용으로 변환이 가능하다.

Ctrl + Shift + Space : 메소드의 가로안에 커서를 놓고 이 키를 누르면 파라미터 타입 힌트를 볼 수 있다.

Ctrl + D : 한줄 삭제

Ctrl + W : 파일 닫기

Ctrl + I : 들여쓰기 자동 수정

Ctrl + Shift + / : 블록 주석(/* */)

Ctrl + Shift + \ : 블록 주석 제거

Ctrl + /여러줄이 한꺼번에 주석처리됨. 주석 해제하려면 반대로 하면 된다.

Alt + Up(Down) : 위(아래)줄과 바꾸기

Alt + Shift + 방향키 : 블록 선택하기

Ctrl + Shift + Space : 메소드의 파라메터 목록 보기

Ctrl + Shift + O : 자동으로 import 하기

Ctrl + Shift + F4 : 열린 파일 모두 닫기

Ctrl + M : 전체화면 토글

Ctrl + Alt + Up(Down) : 한줄(블럭) 복사

Ctrl + , or . : 다음 annotation(에러, 워닝, 북마크 가능)으로 점프

Ctrl + 1 : 퀵 픽스

F3 : 선언된 변수로 이동, 메소드 정의부로 이동

Ctrl + T : 하이어라키 �b업 창 띄우기(인터페이스 구현 클래스간 이동시 편리)

Ctrl + O : 메소드나 필드 이동하기

Ctrl + F6 : 창간 전환, UltraEdit  Editplus Ctrl + Tab 과 같은 기능

 

----- 템플릿 사용 -----

sysout 입력한 후 Ctrl + Space 하면 System.out.println(); 으로 바뀐다.

try 입력한 후 Ctrl + Space 하면 try-catch 문이 완성된다.

for 입력한 후 Ctrl + Space 하면 여러가지 for 문을 완성할 수 있다.

템플릿을 수정하거나 추가하려면 환경설정/자바/편집기/템플릿 에서 할 수 있다.

 

----- 메소드 쉽게 생성하기 -----

클래스의 멤버를 일단 먼저 생성한다.

override 메소드를 구현하려면, 소스->메소드대체/구현 에서 해당 메소드를 체크한다.

기타 클래스의 멤버가 클래스의 오브젝트라면, 소스->위임메소드 생성에서 메소드를 선택한다.

 

----- organize import -----

자바파일을 여러개 선택한 후 소스->가져오기 체계화 해주면 모두 적용된다.

 

----- 소스 코드 형식 및 공통 주석 설정 -----

환경설정 -> 자바 -> 코드 스타일 -> 코드 포멧터 -> 가져오기 -> 프로파일.xml 을 불러다가 쓰면 된다.

또한 다수의 자바파일에 프로파일을 적용하려면 패키지 탐색기에서 패키지를 선택한 후 소스 -> 형식화를 선택하면 된다.

환경설정 -> 자바 -> 코드 스타일 -> 코드 템플리트 -> 가져오기 -> 템플리트.xml 을 불러다가 쓰면 된다.

 

----- 에디터 변환 -----

에디터가 여러 파일을 열어서 작업중일때 Ctrl + F6 키를 누르면 여러파일명이 나오고 F6키를 계속 누르면 아래로

Ctrl + Shift + F6 키를 누르면 위로 커서가 움직인다.

Ctrl + F7 : 뷰간 전환

Ctrl + F8 : 퍼스펙티브간 전환

F12 : 에디터로 포커스 위치

 

 

 

 

 

- 이클립스 자주쓰는 단축키 -

 

Ctrl + / : 주석 처리 - 한 라인/블록에 대해 주석 처리 (추가 및 제거)

Ctrl + L : 특정 라인으로 이동

Ctrl + F6 : Editor 창간의 이동

Ctrl + F7 : View 이동 메뉴

Ctrl + F8 : Prespectives 이동 메뉴

Ctrl + D : 한라인 삭제 - 커서가 위치한 라인 전체를 삭제 한다.

Ctrl + J : Incremental find 이클립스 하단 상태 표시줄에 Incremental find 라고 표시되어 한 글자자씩 누를 때 마다 코드내의 일치하는 문자열로 이동 , 다시 Ctrl + J 를 누르면 그 문자열과 일치 하는 부분을 위/아래 방향키로 탐색이 가능하다.

Ctrl + N : 새로운 파일 / 프로젝트 생성

Ctrl + 1 (빠른교정) - 문 맥에 맞게 소스 교정을 도와 준다. 변수를 선언하지 않고 썼을경우 빨간색 에러 표시되는데 이 단축키를 적용하면 변수에 맞는 선언이 추가 되도록 메뉴가 나타난다.

Ctrl + 0 : 클래스 구조를 트리로 보기

Ctrl + Space :  Cotent Assist - 소스 구문에서 사용 가능한 메소드, 멤버들의 리스트 메뉴를 보여준다.

Ctrl + PageUp , Ctrl + PageDown : Edit 창 좌우 이동 - Edit 창이 여러개 띄워져 있을경우 Edit 창간의 이동 한다.

Ctrl + Shift + Down : 클래스 내에서 다음 멤버로 이동

Ctrl + Shift + M : 해당 객체의 Import 문을 자동 생성 - import 추가 할 객체에 커서를 위치 시키고 단축키를 누르면 자동적으로 import 문이 생성

Ctrl + Shift + O : import 문을 자동 생성 - 전체 소스 구문에서 import 안된 클래스의 import 문을 생성해 준다.

Ctrl + Shift + G : 해당 메서드 / 필드를 쓰이는 곳을 표시 - View 영역에 Search 탭에 해당 메서드 / 필드를 사용하는 클래스를 표시 해준다.

Alt + Shift + R : Refactoring (이름변경) - Refactoing 으로 전체 소스에서 이름변경에 의한 참조 정보를 변경해 준다.

F3 : 선언 위치로 이동

F11 : 디버깅 시작

F8 : 디버깅 계속

F6 : 디버깅 한줄씩 실행(step over)

F5 : 디버깅 한줄씩 실행 함수 내부로 들어감 (step into)

F12 : Editor 창으로 이동 (Debugging 등 자동적으로 포커스가 이동 됐을경우 편리)

Alt + Up , Alt + Down : 줄 바꿈 - 해당 라인을 위 / 아래로 이동 시킨다.

Alt + Shift + S : Source Menu - 소스메뉴 (Import 추가 , Comment 추가 , 각종 Generator 메뉴) 가 나타난다.

Alt + Shift + Up : 블록설정 - 소스 코드를 블록 단위로 설정해 준다.

Alt + Shift + Down : 블록해제 - 소스 코드를 블록 단위로 해제한다.

Alt + Shift + J : 주석 생성 - 해당 메서드/클래스에 대한 주석을 템플릿을 생성해 준다.

sysout + (Ctrl + Space) : System.out.println() 문장 삽입 - 코드 템플릿을 이용해서 소스 구문을 추가

(Windows -> Preferences -> JAVA -> Editor -> Templates 에서 자주 쓰는 소스 구문을 추가시키면 <템플릿 이름> + (Ctrl + Space) 로 소스 문장을 완성 시킬 수 있다.)

Alt + Shift + Z : Surround With 메뉴 - try / catch 문이나 for , do , while 등을 해당 블록에 감싸주는 메뉴가 나타난다.

Ctrl + Shift + F : 코드 포맷팅 - 코드 내용을 문법 템플릿에 맞게 포맷팅(들여쓰기) 해준다.

Ctrl + Alt + Down: 한줄 복사후 아래에 복사 넣기 - Copy&Paste 대체하는 단축키. 커서가 위치한 라인을 복사해 밑줄에 생성해 준다.

Ctrl + Shift +X : 대문자로 변환

Ctrl + Shift + Y : 소문자로 변환

Ctrl + Shift + L : 모든 단축키의 내용을 표시해준다.

Ctrl + Shift + B : 현재 커서 라인에 Break point 설정

Ctrl + Shift + T : 클래스 찾기

 

http://arabiannight.tistory.com/trackback/346

In this post I’ll show how you can take a screenshot of your current Activity and save the resulting image on /sdcard.

The idea behind taking a screenshot actually is pretty simple: what we need to do is to get a reference of the root view and  generate a bitmap copy of this view.

screenshot

Considering that we want to take the screenshot when a button is clicked, the code will look like this:

findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View v) {
       Bitmap bitmap = takeScreenshot();
       saveBitmap(bitmap);
   }
});

 

First of all we should retrieve the topmost view in the current view hierarchy, then enable the drawing cache, and after that call getDrawingCache().

Calling getDrawingCache(); will return the bitmap representing the view or null if cache is disabled, that’s why setDrawingCacheEnabled(true); should be set to true prior invoking  getDrawingCache().

public Bitmap takeScreenshot() {
   View rootView = findViewById(android.R.id.content).getRootView();
   rootView.setDrawingCacheEnabled(true);
   return rootView.getDrawingCache();
}

 

And the method that saves the bitmap image to external storage:

 public void saveBitmap(Bitmap bitmap) {
    File imagePath = new File(Environment.getExternalStorageDirectory() + "/screenshot.png");
    FileOutputStream fos;
    try {
        fos = new FileOutputStream(imagePath);
        bitmap.compress(CompressFormat.JPEG, 100, fos);
        fos.flush();
        fos.close();
    } catch (FileNotFoundException e) {
        Log.e("GREC", e.getMessage(), e);
    } catch (IOException e) {
        Log.e("GREC", e.getMessage(), e);
    }
}

 

Since the image is saved on external storage, the WRITE_EXTERNAL_STORAGE permission should be added AndroidManifest to file:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /


<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/iconSelector">
 
<!-- pressed -->
 
<item android:state_pressed="true" android:drawable="@drawable/btn_icon_hl" />
 
<!-- focused -->
 
<item android:state_focused="true" android:drawable="@drawable/btn_icon_hl" />
 
<!-- default -->
 
<item android:drawable="@drawable/btn_icon" />
</selector>

를 코드로 구현하기

StateListDrawable states = new StateListDrawable();
states
.addState(new int[] {android.R.attr.state_pressed},
    getResources
().getDrawable(R.drawable.pressed));
states
.addState(new int[] {android.R.attr.state_focused},
    getResources
().getDrawable(R.drawable.focused));
states
.addState(new int[] { },
    getResources
().getDrawable(R.drawable.normal));
imageView
.setImageDrawable(states);


<출처 : http://tigerwoods.tistory.com/31>

1. 환경설정 개요 (Preferences)

 

안드로이드 플랫폼은 Data를 저장하는 방법으로 환경설정(이하 Preferences), 파일, Local DB, 네트워크를 제공한다.

 

그 중 Preferences는 가장 간단하게 정보를 저장하는 방법(mechanism)을 제공하며, App이나 그 컴포넌트 (Activity, Service 등)의 환경 설정 정보를 저장/복원하는 용도로 사용된다.

 

 

▌Preferences의 형태▐

안드로이드에서 Preferences는 ListView의 형태로 표현되며 쉬운 Preferences의 구현을 위해 PreferenceActivity 클래스를 제공한다. PreferenceActivity 클래스는 XML 기반의 Preference 정의 문서를 통해 App 파일 저장소에 Preferences 파일을 생성하고 사용하는 방식으로 작동한다. (참고: 일반 Activity를 사용해 ListView형태의 Preference Activity가 아닌 커스텀 Preference Activity를 구현 할 수도 있지만 (Container + 각종 UI 위젯 사용) 이 글에서는 그 방법에 대해 다루지 않으려고 합니다)

 

 

▌Preferences 데이터 저장▐

Preferences를 위한 데이터 저장 시 사용되는 자료구조는 Map 방식이다. 즉 키(key)-값(value) 한 쌍으로 이루어진 1~n개의 항목으로 구성된다. 키(key) 는 String 타입으로, 말 그대로 각 데이터에 접근할 수 있게 하는 유일한 구분자며, 값(Value)은 기본 자료형 (int, boolean, string 등) 기반의 데이터로 구성된다. 다음은 Map 데이터 구조의 한가지 예이다.

 키(Key) - String 형
 값(Value) - 기본자료형
 Font
 "Noraml"
 FontSize  20
 FontColor  FFFFFFFF
 ... ...
 ... ...

 

위와 같은 Preferences데이터는 안드로이드 디바이스의

data/data/App 패키지 이름/shared_prefs/

경로에 XML형태의 파일로 생성된다. xml 파일 이름은, 파일 생성/접근을 위해 어떤 메서드를 사용하느냐에 따라 시스템이 지정 할 수도 있고, 개발자가 임의의 파일 이름을 지정할 수도 있다.

 

 

참고로, 안드로이드에서 Preferences 데이터 파일을 다수의 App간에 공유하는 것은 기본적으로 불가능 하게 디자인 되어 있다. (참고: 여기서 불가능하다는 뜻은 Preferences Framework에서는 타 App의 환경 설정 파일에 접근할 수 있는 메서드를 제공하지 않는다는 뜻이다. 그럴 필요가 있을지 모르겠지만, 굳이 접근해야 한다면 Preferences 파일 생성 모드를 MODE_WORLD_READABLE로 설정해 guest가 파일을 쓰고 읽을 수 있도록 하고(위에서 MyCustomePref.xml 처럼) Preferences 데이터 파일을 파일 스트림을 통해 읽어와 직접 파싱하고 사용할 수 있는 편법이 있긴 하다)

 

XML파일을 디바이스로부터 추출해 열어보면 Preference 데이터가 다음과 같은 형식으로 저장되어 있음을 볼 수 있다.

1 <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
2 <map>
3     <boolean name="GreetingMsg" value="true" />
4     <boolean name="sub_checkbox" value="true" />
5     <string name="AdditionalMsg">tigerwoods.tistory.com</string>
6     <string name="TextColor">FF00FF00</string>
7     <string name="Ringtone">content://settings/system/ringtone</string>
8 </map>

 

 

Preferences 구현 시 위와 같은 XML 기반 Preferences 데이터 파일을 File I/O API를 사용해 직접 생성/사용 하는 것은 아니다. 안드로이드는 Preferences구현/운용을 위해 android.preference 패키지 및 몇 가지 유용한 클래스/인터페이스(PreferenceActivity, SharedPreferences 등)를 제공하는데, 그것들의 도움을 받으면 손쉽게 Preferences를 구현/운용 할 수 있다.

 

 

▌Preferences 구현▐

안드로이드에서는 Preference 구현을 위해 많은 관련 클래스를 제공하기 때문에 그 클래스들을 적절히 이용하여 다음과 같이 몇 가지 단계만 추가하기만 하면 쉽게 프로젝트에 Preferences 기능을 추가 할 수 있다.

  • 프로젝트 내 어떤 환경정보를 Preferences로 관리 할지 설계
  • 위 설계에 따라 Preferences XML 문서를 작성하고 프로젝트 리소스로 공급 (\res\xml\ 밑)
  • 작성된 Preferences XML문서를 사용하는 PreferenceActivity 구현
  • Preferences가 필요한 Activity로부터 PreferenceActivity를 호출 (Intent 이용)
  • App이 다시 실행될 때 이미 저장된 Preference데이터 파일이 있다면 onResume()과 같은 callback내부에서 환경 설정 값 설정을 불러와 사용

 

위에 나열된 사항 이외에 Preference 데이터 파일의 생성, 데이터 변경 이벤트 listener, 데이터의 저장과 같은 부분은 PreferenceActivity 클래스가 자동으로 관리해 줌으로 개발자는 별로 신경 쓸 필요가 없다. (참고: PreferenceActivity를 사용하지 않고 일반 Activity로 구현한다면 안드로이드가 제공하는 여러 Preferences 관련 클래스 등을 이용해 파일 생성, 이벤트 listener, 저장 등을 직접 구현 해야 한다.)

 

그럼 위 Preference 구현 5단계 중 두 번째인 Preference XML 문서 작성부터 한 번 살펴보자.

 

 

 

 

2. Preference XML 문서 작성하기

 

Preference XML 문서를 작성하기 전에 문서를 구성할 Preferences 관련 클래스들에 관해 약간의 지식이 필요함. 이 글 끝 부분에 "참고. Preference XML 문서 작성시 사용되는 클래스" 단락에 간단한 클래스 설명과 중요 XML 속성, 메서드 등이 설명 되어 있음으로 참고.

 

Preferences XML 문서는 프로젝트 폴더\res\xml\파일이름.xml의 위치에 생성 한다. xml 파일의 이름은 개발자 임의로 지정할 수 있으며 추 후 code 상에서 R.xml.파일이름으로 접근 할 수 있다.

 

이렇게 생성된 XML 문서 내부를 여러 Preference 관련 클래스를 사용해 정의 해야 하는데, 이 글에 포함된 예제 프로젝트에서 사용된 Preference XML 문서를 살펴보면 다음과 같다.

접기

01 <?xml version="1.0" encoding="utf-8"?>
02 <PreferenceScreen
03     xmlns:android="http://schemas.android.com/apk/res/android"
04     android:key="preference_root" >
05     <CheckBoxPreference
06         android:key="GreetingMsg"
07         android:title="추가 인사말 표시"
08         android:summary="Check하면 추가 인사말을 표시 합니다"
09         android:defaultValue="true" /> 
10     <EditTextPreference
11         android:key="AdditionalMsg"
12         android:title="추가 메시지 입력"
13         android:summary="인사말 밑에 표시될 추가 메시지를 입력 합니다" />
14     <PreferenceCategory
15         android:key="category1"
16         android:title="Category 1 제목"
17         android:summary="Category 1에 대한 자세한 설명: 표시 안됨"
18         android:enabled="false" >  
19         <ListPreference
20             android:key="TextColor"
21             android:title="텍스트 Color"
22             android:summary="인사말과 추가메시지의 글자 색을 지정합니다"
23             android:entries="@array/entries_color"
24             android:entryValues="@array/entryValues_color"
25             android:defaultValue="FFFFFFFF" />
26         <RingtonePreference
27             android:key="Ringtone"
28             android:title="Ringtone 설정"
29             android:showDefault="true"
30             android:showSilent="true"
31             android:ringtoneType="all"
32             android:summary="사용할 Ringtone을 설정 합니다" />
33     </PreferenceCategory>
34     <PreferenceCategory
35         android:key="category2"
36         android:title="Category 2 제목" >
37         <PreferenceScreen
38             android:key="preference_sub"
39             android:title="Sub Preferences 가기"
40             android:summary="클릭하면 Sub Preferences가 호출 됩니다" >
41             <CheckBoxPreference
42                 android:key="sub_checkbox"
43                 android:title="Sub CheckBox Preference"
44                 android:summaryOn="Uncheck하면 Main Preference의 Category2 밑의 요소 비활성화"
45                 android:summaryOff="Check하면 Main Preference의 Category2 밑의 요소 활성화" />
46         </PreferenceScreen>
47     </PreferenceCategory>
48 </PreferenceScreen>

접기

 

 

그럼 settings.xml에서 사용된 요소들을 하나씩 분석해 보자

 

 

<PreferenceScreen>

Preferences XML 문서의 root 요소는 항상 <PreferenceScreen>이며 root 요소 내부에는 반드시 xmlns 속성 (xmlns:android=http://......)을 정의해 주어야 한다.

 

root로 사용된 <PreferenceScreen>는 child를 포함하는 container 역할을 하며, PreferenceActivity에게 Preferences 계층 구조를 전달하는 역할을 한다. root로 사용된 <PreferenceScreen>은 container역할 만 하고 자기 자신은 화면에 직접 표현이 안됨으로 title, summary 속성을 지정할 필요가 없다.

 

key 속성에 지정된 문자열은 추 후 code에서 이 preference 계층 구조 내부의 특정 요소를 찾을 수 있는 키 값이 된다.


Preferencescreen root = (PreferenceScreen)getPreference("preference_root");

CheckBoxPreference cb = (CheckBoxPreference)getPreference("checkbox");

 

 

<CheckBoxPreference> & <EditTextPreference>

Root <PreferenceScreen> 밑에 처음 나오는 두 개의 child이며, XML에 추가된 순서대로 Preference view에 표시된다.

각 아이템은 key, title, summary 속성이 지정되어 있으며, checkbox의 경우 초기 설정이 check 상태로 지정되어 있다.

<EditTextPreference> 같은 경우 DialogPreference로부터 상속하며, 클릭 시 별도의 다이얼로그 창을 popup 한다.

 

 

<PreferenceCategory>

예제는 총 2개의 <PreferenceCategory>가 있으며 첫 번째는 <ListPreference>와 <RingtonePreference>를 하나의 Category로 묶는다. 예제 XML의 34번째 라인에 보면 두 번째 Category도 지정되어 있으며 <PreferenceScreen>을 Catefory 멤버로 가지고 있다.

 

<PreferenceCategory>에서 title속성은 다른 Preference 객체와 같이 제목을 표시하지만, summary는 지정되어도 화면에 표시 되지 않는다.

 

또, 첫 번째 category의 속성 중 android:enabled 속성이 false로 지정되어 있음으로 Preference초기 실행 시 child인 <ListPreference>와 <RingtonePreference>는 비활성화 상태로 표현된다. 이와 같이 여러 Preference 아이템을 하나의 category로 묶으면 그룹 속성을 지정하는 것이 가능하다. (Preference의 마지막 항목, checkbox를 check하면 첫 번째 category를 활성화 함)

 

 

<ListPreference>

다른 항목들과 마찬가지로 key, title, summary가 설정되어 있으며, entries 속성에는 사용자에게 보일 ListView 아이템을 지정하며, entryValues속성에는 컴퓨터가 처리할 처리 할 정보를 제공한다. 예를 들면 entries – "Red", "Green", "Blue" 등 사람이 읽을 수 있는 값을 제공하고, entryValues에는 "FFFF0000", "FF00FF00", "FF0000FF" 등 컴퓨터가 사용할 정보를 제공한다.

 

또, android:defaultValue 속성이 "FFFFFFFF"으로 지정되어 있어 사용자가 설정 값을 바꾸기 전까지는 "FFFFFFFF" (흰색)이 기본 값으로 사용된다.

 

 

<RingtonePreference>

key, title, summary가 지정되어 있으며, showDefault와 showSilent가 true로 설정되어 Default 항목과 Slient 항목이 리스트에 표시된다. (<RingtonePreference>는 사용자가 지정한 정보를 바탕으로 Ringtone 설정 기능을 제공하는 Activity들을 찾는 Intent를 발생 시킨다. 디바이스에 여러 종류의 ringtone과 ringtone 관련 activity를 제공하는 app이 설치되어 있다면 더 많은 ringtone 옵션이 화면에 표시 될 수도 있다)

 

 

<PreferenceCategory>

두 번째 그룹으로 CheckBox를 별도의 화면에 표현하는 <PreferenceScreen> child를 포함.

 

 

<PreferenceScreen>

<PreferenceScreen>가 child 요소로 사용된 경우. Preferences에서 하나의 아이템으로 표현되기 때문에 title, summary가 지정되어 있다. 이 아이템을 선택하면 별도의 Preference 창 (dialog 기반)이 Popup한다.

 

 

<CheckBoxPreference>

key, title이 지정되어 있으며, summayOn과 summaryOff에는 각각 check상태의 도움말과 uncheck상태의 도움말이 설정 되어 있다.

 

 

 

 

3. XML + PreferenceActivity이용해 Preference 구현하기

 

작성된 Preference XML 파일을 화면에 표현 가능한 Preference로 만드는데 핵심적인 역할을 하는 것이 바로 PreferenceActivity이다. 전에 살펴본 적이 있는 ListActivity, TabActivity와 마찬가지로 Activity로 상속하며, Preference를 화면에 표현하고 운영하는데 특화된 클래스이다.

 

PreferenceActivity는 Preference 생성/운영에 도움이 되는 다음과 같은 편리한 기능이 구현되어 있다.

  • XML부터 Preference 계층 구조를 전달 받는 메서드 제공
  • 타 Activity의 Preference 계층 구조를 받아오는 메서드 제공
  • 제공된 Preference 계층 구조를 ListView 형태로 자동 구성/표현
  • 제공된 Preference 계층 구조에 따라 디바이스에 Preference 데이터 파일 자동 생성
  • 사용자가 변경한 사항들을 Preference 데이터 파일에 자동 저장

 

 

PreferenceActivity를 이용해 Preference를 구현하려면 크게 두 가지 단계만 거치면 되는데 다음과 같다. (물론 PreferenceActivity도 Activity 생명 주기를 따르기 때문에 onPause(), onResume() 메서드 같은 생명 주기 관련 callback의 구현도 신경 써야 하지만 여기서는 Preference를 구현하는 최소한의 기능만 설명한다)

  • PreferenceActivity를 상속하는 커스텀 클래스 정의.
  • onCreate()에 내부에서 Preference 계층 구조를 가져오는 메서드 호출.

 

위에서 두 번째 구현 순서로 설명된 Preference 계층 구조를 가져오는 메서드는 2개가 제공된다.

우선, \res\xml 밑의 Preference XML로부터 Preference 계층 구조를 얻어올 수 있는 메서드는 다음과 같다.

void addPreferencesFromResource(int)

Parameter:

  • int: Preference XML Resource. (예. R.xml.preferences)

 

두 번째로 타 Activity가 사용하는 Preference 계층 구조를 가져와 적용 시킬 수도 있는데, 이 때 사용되는 메서드는 다음과 같다.

(이 부분은 해결이 안 되는 중요한 버그가 있습니다. main preference까지는 잘 가져와 지는데 별도의 dialog를 띄우는 Preference 아이템을 클릭하면 WindowsManager가 BadTokenException을 발생합니다. 자세한 증상은 첨부된 예제 프로젝트에서 확인하시고, 혹시 해결 방법을 아시는 분은 꼭 댓글 부탁 드립니다)

void addPreferencesFromIntent(Intent)

Parameter:

  • Intent: Preference 계층 구조를 제공할 Activity를 지정하는 Intent

 

구현이 완료된 PreferenceActivity는 타 Activity로 부터 호출 되면 ListView 형태의 Preference 화면을 사용자에게 제공하고, 사용자가 정보를 수정하면 자동으로 Preference 데이터에 저장 하게 된다.

 

PreferenceActivity가 많은 편의 기능을 제공하지만 저장된 설정 복원은 개발자의 몫이다. 즉, 포함된 App이 다시 실행될 때, 기존에 저장된 Preferences설정 값을 Preference 데이터 파일로부터 읽어와 App에 적용 시키는 작업 (ex. App시작 시 Preference 파일에 저장된 폰트 색깔 등을 로드 해 App에 적용시키는)은 개발자가 직접 구현해 주어야 한다. 이를 위해 다음 단락에는 저장된 Preference 데이터 파일에 접근하여 저장된 정보를 가져오는 방법에 대해 알아본다.

 

 

 

 

4. Preference 데이터 파일로부터 환경 복원하기

 

안드로이드에서는 SharedPreferences 인터페이스를 이용해 저장된 Preferences 데이터 파일에 접근한다.

 

 

▌SharedPreferences 인터페이스 얻기▐

 

안드로이드 코드 내에서 SharedPreferences 인터페이스를 얻는 방법은 다음 나열된 세 개의 메서드를 통해서 가능하다. 



SharedPreferences Activity.getPreferences(int)

Parameter:

  • int: Preference 데이터 파일의 생성 시 접근 권한을 설정. Context클래스에 상수로 선언된 MODE_PRIVATE, MODE_WORLD_READABLE, MODE_WORLD_WRITABLE을 설정 가능. MODE_PRIVATE는 지금 App 만 접근 가능하도록 파일을 생성하며 (_rw_rw____), MODE_WORLD_READABLE/WRITABLE은 타 App에서도 파일을 읽고/쓸 수 있도록 생성한다 (_rw_rw_r__ 또는 _rw_rw__w_ 또는 |를 이용해 두 속성을 모두 지정하면 _rw_rw_rw_)

Return:

  • SharedPreferences: 메서드가 호출되는 Activity 클래스의 이름으로 저장된 Preference 데이터 파일의 SharedPreferences 인터페이스를 리턴. 같은 이름의 Preference 데이터 파일이 없을 경우 새로 생성

 

예를 들면 Activity를 상속하는 A, B라는 클래스 이름의 커스텀 Activity가 정의 되어 있다. A 내부에서 getPreference를 호출할 경우 디바이스의 Preference 저장 위치(data/data/패키지이름/shared_prefs/)에 A.xml 이라는 Preference 데이터 파일을 검색하고 파일이 존재 한다면 해당 파일에 대한 SharedPreferences 인터페이스를 리턴 하며, 파일이 존재하지 않을 경우에는 A.xml 파일을 생성한 후 생성한 파일에 대한 SharedPreferences 인터페이스를 리턴한다. 마찬가지로, B 내부에서 getPreference를 호출할 경우 디바이스에서 B.xml 파일을 검색 후 해당 파일에 대한 SharedPreferences 인터페이스를 리턴 한다 (없으면 파일 생성 후 인터페이스 리턴).


 

SharedPreferences Context.getSharedPreferences(String, int)

Parameter:

  • String: 사용(생성)할 Preference 데이터 파일의 이름을 지정
  • int: 생성될 Preference 데이터 파일의 생성 시 접근 권한을 설정

Return:

  • SharedPreferences: 첫 번째 인자로 전달되는 문자열과 일치하는 이름의 Preference 데이터 파일의 SharedPreferences 인터페이스를 리턴. 만약 같은 이름의 파일이 없다면 파일을 새로 생성하고 생성된 파일에 대한 SharedPreferences 인터페이스를 리턴

 

한가지 참고할 사항은 이 메서드를 통해 개발자가 생성/사용될 Preference 데이터 파일의 이름을 임의로 설정할 수 있다는 것인데, 예를 들어, A라는 Activity에서 getSharedPreferences("initial_setting", MODE_PRIVATE)라는 메서드를 실행한다면, preference framework는 /data/data/패키지/shared_pref/ 밑에서 initial_setting.xml이라는 파일을 검색하고 없다면 이를 새로 생성 후 initial_setting.xml에 대한 SharedPreferences 인스턴스를 리턴 하게 된다..

 

제일 처음에 설명한 Activity.getPreferences(int)도 실제로는 자기자신을 호출한 Activity(또는 컴포넌트)의 이름과 입력된 int 형 인자를 가지고 본 메서드를 호출하는 형태로 구현되어 있다.




Static SharedPreferences PreferenceManager.getDefaultSharedPreferences(Context)

Parameter:

  • Context: 사용하기 원하는 SharedPreferences 인터페이스를 포함하는 Context 지정

Return:

  • SharedPreferences: 인자로 지정된 Context (app 구동 환경)가 기본으로 생성하는 Preference 데이터 파일에 대한 SharedPreferences를 리턴

 

이 메서드는 App level의 기본 Preference 데이터 파일을 생성/사용하는데 사용된다. 예를 들어 com.holim.test.aaa 라는 패키지에 포함되는 A Activity에서 본 메소드를 호출 하면 Preference 프레임웍은 /data/data/패키지이름/shared_pref/com.holim.test.aaa_preferences.xml 이라는 Preference 데이터 파일을 생성한다. 전달된 Context 인자에 따라 패키지이름_preferences.xml 형태로 생성/사용할 Preference 데이터 파일의 이름이 시스템에 의해 자동으로 지정되게 되기 때문에, 호출되는 위치가 어디이든 같은 Context를 인자로 전달해 호출한 getDefaultSharedPreferences(…)메서드는 항상 같은 SharedPreferences 인스턴스를 리턴한다.

 

한가지 참고할 사항은 본 메서드는 static 메서드이기 때문에 메서드를 제공하는 PreferenceManager 클래스를 따로 생성할 필요 없이 다음과 같이 사용하면 된다.

SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);

 

 

이와 같이 여러 메서드를 사용해 얻어온 SharedPreferences 인터페이스로 무엇을 할 수 있는 지 알아 보자. 다음은 SharedPreferences 인터페이스가 제공하는 중요 메서드들이다.

 

 

▌SharedPreferences 인터페이스▐

 

SharedPreferences 인터페이스가 제공하는 기능은 다음과 같다.

  • 디바이스에 저장되어 있는 Preference 데이터 파일로부터 데이터를 추출하는 여러 메서드
  • Preference 데이터를 수정하는 방법을 제공하는 Editor 내부 인터페이스
  • Preference 데이터가 변경되었을 때 호출되는 callback 인터페이스

 

중요 메서드

SharedPreferences.Editor edit() – SharedPreferences가 연결된 Preference 데이터 파일을 수정 할 수 있게 여러 메서드를 제공하는 Editor 인터페이스 리턴. Editor 인터페이스는 자료 수정을 완료 한 후 Editor.commit() 메소드를 사용해야 파일에 변경내용이 저장됨.

void registerOnSharedpreferenceChangeListener(…) – Preference 변경 이벤트 Listener 등록

void unregisterOnSharedPreferenceChangeListener(…) – 위에 등록한 이벤트 Listener 등록 해지

Map <String, ?> getAll() – 모든 Preferences 값을 Map 자료형으로 리턴

boolean contains(String) – 인자로 제공되는 문자열과 같은 key의 Preference 항목이 있으면 true 리턴

타입 get타입(String, 타입) – Boolean, Float, Int, Long, String 형 메서드가 있으며 첫 인자는 데이터를 가져올 Key 값이며, 두 번째 인자는 찾는 preference 데이터가 존재하지 않을 때 리턴 할 값이다.

Boolean isMarried = sharedPref.getBoolean("결혼여부", false);

int fontSize = sharedPref.getInt("폰트사이즈", 20);

 

중요 인터페이스

  • SharedPreferences.Editor – Preference 파일에 저장된 항목을 수정/제거 할 수 있는 여러 메서드를 제공하는 인터페이스
  • SharedPreferences.OnSharedPreferenceChangeListener – Preference 데이터가 변경되었을 때 호출되는 callback 인터페이스

 

 

예제 프로젝트 중 MainActivity.java의 onCreate(…)와 onPause(…)메서드에서 보면 지금까지 설명한 SharedPreferences 인터페이스 얻기와 인터페이스가 제공하는 여러 메서드를 사용해 Preference 데이터 파일로부터 저장된 정보를 가져와 사용하는 것을 볼 수 있다.


MainActivity.onCreate()

소스 펼치기

 

MainActivity.onResume()

소스 펼치기

 

 

 

 

참고. Preference XML 문서 작성시 사용되는 클래스

 

전 글들에서 다루었던 XML을 이용한 UI메뉴 구성에서도 봤듯이

XML을 이용하면 디자인과 Logic을 분리 시킬 수 있어 간결한 코드의 구성이 가능할 뿐 아니라 현지화/유지보수 등 작업을 하는데 훨씬 편리 할 수 있음으로 권장되는 구현 방식 이라고 설명 한 적이 있다.

 

Preference에서도 마찬가지로 XML을 이용해 Preferences 구조를 쉽게 정의할 수 있다.

 

Preference XML 문서 작성시 android.preference 패키지 밑의 여러 클래스가 사용되는데 패키지 내부의 중요한 클래스의 상속 구조는 다음과 같다.

 

 

붉은 색 사각형 내부의 클래스가 Preferences 과 직접적으로 연관이 있는 클래스이며, 그 중 Preference 클래스를 비롯한 하위 클래스들이 XML Preference 문서 작성에 사용된다.

 

그럼 Preference 클래스부터 한번 살펴보자

 


 

▌Preference 클래스▐

여러 Preference 관련 클래스의 최상위 부모 클래스 이므로, 제공하는 XML 속성, 메서드는 자식 Preferences 관련 클래스에서도 모두 사용할 수 있다. XML 내부에서 직접적으로 사용되지는 않는다.

 

중요 XML 속성

  • android:key – Preferences 데이터 저장/불러올 때 사용되는 키(key) 지정
  • android:title – Preference 항목의 제목을 지정
  • android:summary – Preference 항목을 자세히 설명하는 문자열 지정
  • android:enabled – Preference 항목의 활성화 비활성화 여부 지정 (true/false)
  • android:selectable – Preference 항목의 선택 가능 여부 결정 (true/false)
  • android:order – Preference 항목이 표시되는 순서를 결정 (0-based 정수 사용: 낮은 값 먼저 보임). 순서가 명시적으로 지정되지 않는다면 XML에 정의된 순서대로 표시됨

 

중요 메서드

  • void setKey/Title/Summary(…) – Preference 항목의 키/제목/설명을 지정
  • void setEnabled(boolean) - Preference항목의 활성화/비활성화 여부 지정
  • void setSelectable(boolean) – Preference 항목의 선택 가능 여부 결정
  • void setLayoutResource(int) – Preference 항목에 표시할 View를 R 클래스로부터 지정
  • void setOnPreferenceChangedListener(…) – Preference 항목에 변화가 있으면 호출될 callback을 지정
  • void setOnPreferenceClickListener(…) – Preference 항목에 click 이벤트가 발생하면 호출될 callback을 지정

 

중요 인터페이스

  • Preference.OnPreferenceChangeListener - 사용자가 Preference를 변경하였을 때 호출되는 callback에 대한 인트페이스
  • Preference.OnPreferenceClickListener - 사용자가 Preference를 클릭하였을 때 호출되는 callback에 대한 인터페이스

 

 


▌PreferenceGroup 클래스▐

 

Preferences를 구성하는 객체들을 담을 수 있는 Container 클래스. PreferenceCategory와 PreferenceScreeen을 파생. XML 내부에서 직접적으로 사용되지는 않음.

 

중요 메서드

  • boolean addPreference(Preference) – 새로운 Preference 항목을 그룹에 추가. 추가 성공/실패 리턴.
  • Preference findPreference(String) – 인자로 전달되는 문자열과 같은 key값을 갖는 group 내 (자기자신포함) Preference 항목 리턴
  • Preference findPreference(int) – 인자(0-based정수)에 명시된 위치의 Preference 항목 리턴. 기본적으로 가장 처음 추가된 항목이 index 0
  • void removeAll() – 이 그룹 내 모든 Preference 항목을 삭제함
  • void removePreference(Preference) - 인자로 전달된Preference 항목을 삭제함
  • void setEnabled(boolean) – 그룹 전체의 활성화/비활성화 여부 지정
  • void setOrderingAsAdded(boolean) – true일 경우 그룹에 추가된 순서로 표현. false일 경우 Preference아이템 내 순서 정보가 있으면 그걸 따르고 순서 정보가 없으면 Preference 아이템의 title을 이용해 알파벳순 정렬.

 


 

▌PreferenceScreen 클래스▐

 

PreferenceGroup에서 파생하며, 실제로 Preference XML 문서 내부에 사용 되는 클래스이다. 여러 Preference 구성 요소를 담을 수 있는 root container역할을 하며 Activity에 표현될 Preference의 계층구조를 나타내는 클래스이다. PreferenceActivity에 전달되어 이 클래스의 인스턴스가 포함하는 것들을 화면에 표현한다.

 

이 클래스가 Preference XML 문서에서 사용될 때는 다음과 같이 두 가지 용도로 사용된다.

  • Preference XML 문서의 root 요소 사용 시 - 경우에는 PreferenceActivity에 전달되어 preference의 전체 구조를 전달하는 용도로 사용. 이 경우 자기 자신은 화면에 표현 안되고 child 요소를 포함하는 Container의 용도로 사용됨 (XML UI에서 Containter는 화면에 표현 안되고 것과 같은 의미)
  • Preference XML 문서에서root가 아닌 child 요소로 사용 시 - Preference 아이템 중 하나로 취급. Preference 아이템 중 하나로 화면에 표시되며, 클릭 시 별도의 preference 화면으로 이동

 

!!! 그림 (main pref. -> sub pref)

 


 

▌PreferenceCategory 클래스▐

 

Preferences를 구성하는 객체들을 특정 category별로 분류 할 수 있게 하는 클래스. 중요한 XML 속성이나 메서드 없음

!!! 그림 (category 분류)

 

 


▌DialogPreference 클래스▐

 

모든 dialog 기반 Preference 객체의 부모 클래스. Preference 화면에는 링크만 표시되고, 링크를 클릭했을 때 실제 Preference를 컨트롤 할 수 있는 Dialog가 popup한다.

!!! 다이얼로그 그림

 

중요 XML 속성

  • android:dialogIcon – Dialog의 Icon 지정
  • android:dialogTitle – Dialog의 제목 지정
  • android:dialogMessage – Dialog 내에 표시될 문자열 내용 지정
  • android:negativeButton – 부정 버튼에 표시될 Text설정 (취소, cancel 등)
  • android:positiveButton – 긍정 버튼에 표시될 Text 설정 (적용, OK 등)

 

중요 메서드

  • void setDialogIcon(Drawable) – Dialog의 Icon 지정
  • void setDialogTitle(…) – Dialog의 제목 지정. 문자열 직접 지정 또는 문자열 리소스 사용
  • void setDialogMessage(…) – Dialog 내에 표시될 문자열 내용 지정
  • void setPositiveButtonText(…) – 부정 버튼에 표시될 Text설정 (취소, cancel 등)
  • void setNegativeButtonText(…) – 긍정 버튼에 표시될 Text 설정 (적용, OK 등)

 


 

▌EditeTextPreference 클래스▐

 

DialogPreference로부터 상속하며, 사용자로부터 문자열을 입력 받아 저장 하기 위한 Preference 아이템을 구현한 클래스.

 

중요 메서드

  • EditText getEditText() – Dialog가 포함하는 EditBox인스턴스를 리턴
  • String getText() – SharedPreferences 객체로부터 저장된 문자열 리턴
  • void setText() – SharedPreferences에 문자열 저장

 


 

▌ListPreference 클래스▐

 

DialogPreference로부터 상속하며, Dialog기반의 ListView 위젯에 미리 제공되는 문자열들을 표현하고 사용자가 그 문자열 중 하나를 선택하여 설정 값을 지정할 수 있도록 구현한 클래스.

 

중요 XML 속성

  • android:entries – ListView의 각 raw에 표현될 문자열을 array 리소스를 통해 공급. 사용자(human)를 위한 정보.
  • android:entryValues – ListView의 특정 raw가 사용자에 의해 선택되었을 때 프로그램 내부적으로 처리 할 문자열 data를 array 리소스 형식으로 제공. 컴퓨터가 처리 하기 위한 정보. (ex. 남/여: 사람을 위한 정보 -> 0/1: 컴퓨터를 위한 정보)
  • android:defaultValue – 초기 선택 항목 지정. (0-based 정수)

 

중요 메서드

  • CharSequence[] getEntries() – ListView의 각 row에 표현될 문자열들을 리턴 (사용자 위한 정보)
  • CharSequence[] getEntryValues() – Entry(사람을 위한 정보)와 연계된 컴퓨터가 처리할 문자열들을 리턴
  • void setEntries(…) – ListView의 각 row에 표현할 문자열 지정. Array리소스 또는 CharSequence[] 전달
  • void setEntryValues(…) – 컴퓨터가 처리할 문자열 지정. Array또는 CharSequence[] 전달

 


 

▌CheckBoxPreference 클래스▐

 

CheckBox 기능의 Preference 아이템을 구현한 클래스. SharedPreferences에 boolean 값으로 정보 저장

 

중요 XML 속성

  • android:summayOn – CheckBox가 check상태일 때 사용자에게 보일 안내문
  • android:summaryOff – CheckBox가 uncheck 상태일 때 사용자에게 보일 안내문

 

중요 메서드

  • boolean isChecked() – 현재 check/uncheck 상태 리턴
  • void setChecked(boolean) – CheckBox를 program 상에서 check/uncheck 함
  • void setSummaryOn(…) – CheckBox가 check상태일 때 사용자에게 보일 안내문 설정. String 리소스나 CharSequence 인스턴스 전달
  • void setSummaryOff(…) – CheckBox가 uncheck상태일 때 사용자에게 보일 안내문 설정

 


 

▌RingtonPreference▐

 

사용자가 디바이스에서 제공하는 전화 벨 소리를 지정할 수 있게 구현된 클래스. Intent를 이용해 어떤 벨 소리 Picker를 화면에 표시할 지 결정함.

 

중요 XML 속성

  • android:ringtoneType – 어떤 종류의 벨 소리 종류를 선택 가능하게 할지 지정. ringtone, notification, alarm, all 중에 하나 또는 복수(| 사용)개 지정 가능
  • android:showDefault – Default 벨소리 항목 표시 여부 결정 (true/false)
  • android:showSilent – 무음(Silent) 항목 표시 여부 결정 (true/false)

 

중요 메서드

  • setRingtoneType(int type) – XML 속성 중 ringtoneType에 대응. RingtoneManager 클래스에 선언되어 있는 상수 TYPE_RINGTONE, TYPE_NOTIFICATION, TYPE_ALARM, TYPE_ALL 중 하나 또는 복수(|사용) 전달
  • setShowDefault(boolean) – XML showDefault 속성에 대응
  • setShowSlient(boolean) – XML showSilent 속성에 대응

 


 

PreferenceManager 클래스

 

Activity나 XML로부터 Preferences 계층구조의 생성을 돕는 helper 클래스이지만 이 클래스를 이용하여 직접 Preferences를 구성하기보다는 PreferenceActivity.addPreferenceFromResource(int) 또는 PreferenceActivity.addPreferenceFromIntent(Intent)를 사용하는 것이 일반적이다.

 

혹시, PreferenceActivity를 사용하지 않고 Activity를 이용해 직접 Preferences 화면을 구성해야 한다면 필요한 클래스이다.

 

중요 메서드

  • Preference findPreference(CharSequence) – 인자로 전달되는 Key값을 가지는 Preference 항목의 인스턴스를 가져옴
  • SharedPreferences getDefaultSharedPreferences(Context) – 제공되는 context가 기본으로 사용하는 Preference파일로부터 SharedPreferences인스턴스 생성해 리턴
  • SharedPreferences getSharedPreferences() – this객체가 사용하는 context와 연결될 Preference파일로부터 SharedPreferences 인스턴스 생성해 리턴
  • void setDefaultValues(Context, int resId, boolean) – 제공되는 context가 사용하는 SharedPreferences를 두 번째 인자로 제공되는 XML 리소스에 정의된 기본 속성값들로 설정 함. 세 번째 인자가 false 일 경우엔 이 메서드가 전에 실행된 적이 있다면 무시하고, true일 경우 전에 실행 여부화 상관없이 재 실행된다. 참고: 마지막 인자를 true로 지정 했다고 해서 이 메서드를 이용해 Preferences를 바로 초기화 할 수 있는 것은 아니고, SharedPrefernces.clear() 메서드를 사용해 Preferences 항목을 모두 삭제 후 이 메서드를 사용해 XML에 지정된 본래의 값으로 Preferences를 초기화 할 수 잇다.


어디서 주은 PPT인데 나름 괜찮은 설명들이 있긴 하다.

 

 

07. 고급 위젯.pptx

 

출처 : http://blog.daum.net/maleny/89

 

최근에 인터넷을 배회하다가 저가의 타블렛 PC 하나를 발견하고 앱 개발 테스트용으로 사용하고
다니면서 E-BOOK 이나 읽을 용도로 구입했다.

자체 데이터 통신 기능도 없고 전화기능도 없고 당연히 DMB도 안되고 GPS 도 안되지만
KT 의 Egg 를 들고 다니니까 Wi-Fi 를 이용한 웹 서핑 등 여러가지 필요는 충족 시킬 수 있을 것이라 판단했다.

일단 인터넷 연결을 통한 여러가지 필요한 작업은 만족 스러웠고
AlphaBiz 의 Mobile Client 역활도 잘 수행한다.
다만 스마트폰의 사이즈에 맞게 앱을 만들엇기에 큰 기기에서는 화면이 영 개판 오분..

하긴 이 타블렛을 산 이유도 이 개판 오분전을 잡기 위해서 이었지만.

문제는 이 타블렛 PC 가 컴퓨터에 설치가 안된다는 것이다.
물론 USB 케이블로 연결해 외장메모리로 사용은 된다.
그러나 앱을 개발해서 탑재하고 돌릴려면 기기가 컴퓨터에 설치가 되어야 하는데..
판매처에서 드라이버를 제공하지 않는다.

아니 제공은 커녕 게시판에 문의를 해도 도통 무슨 말을 하는지 이해 조차 하지 못하니..

여태까지는 뭐 삼숑, 엘디, 쓰까이 등등 유명 브랜드들이 만든 고가의 고급 제품들이 주류를 이루고 있고
당연히 필요한 모든 드라이버나 유용한 앱들을 기본적으로 제공되고 있고 사용자 모임 같은 카페도
활성화 되어있고 개발자들도 유명 브랜드 기기를 위주로 정보를 제공하고 있으니 문제가 없겠지만

앞으로 이처럼 저가의 보급형 기기가 쏟아져 나오게 되면
이와 같은 문제가 많이 발생할 것으로 여겨진다.

개발자들이 프로그램 개발에 대해서는 빠삭하지만 하드웨어 쪽으로 들어가면 버벅거리는 경우가 다수이고
또 초보자들이 야심차게 구입해서 앱 개발을 하려고 하는데 가장 기본적인 문제가 해결이 안되면
의욕도 상실하게 되고 성질 뻗치게 되고 마음 상하고 몸 상하는 비극이 초래할 수도 있다.

사실 나도 이 문제를 해결하기 전까지는 18 이 분당 수십번 입에서 발사되고
웹사이트를 헤메고 다니다 그냥 성질 뻗쳐서 확 컴퓨터를 꺼 버리고.
판매자 사이트에 성질도 부리고..

결국 약간씩의 정보를 모으고 모으고 검색하고 접근해 들어가다가
영문 사이트 (게시물이 딱 하나 있드만)를 찾아냈고 해석하고 분석하고 해보고 결국에 성공했다.
성공하자 마자 그냥 컴퓨터를 확 꺼버리고 퇴근했다.

아 친절한 알파비즈는 이렇게 발견한 정보를 친절하게 강좌한다. 것도 아주 디테일 하게..

일단 안드로이드 기기에서 개발자옵션 에서 USB 가 연결되었을 경우 디버그 모드로 연결 될 수 있도록 해준다. (기본상식)
다음 컴퓨터와 USB 를 연결하면 드라이버를 설치하라고 나온다.
아무리 찾아 봐도 맞는 드라이버를 찾을 수 없다.
설치하지 않고 끝내 버린다.

그리고 제어판의 시스템 -> 하드웨어 -> 장치관리자를 보면

 

이렇게 기타 장치에 Android 라고 드라이버가 설치되지 않은 상태이거나


 


이렇게 Android Phone 아래에 Android 라고 드라이버가 설치되지 않은 상태로 표시가 되어있다.


자 위의 어떤 경우가 되었가나 암튼 Android 기기를 선택하고 마우스 오른쪽 버튼을 눌러서 속성을 선택한다.

 

 


위와 같이 Android 등록정보 창이 뜨면 '자세히'라는 탭을 선택하고 밑의 셀렉션에서 하드웨어 ID 를 선택한다.
그러면 위와 같이 두 줄의 뭔가가 나타나게 된다.
그냥 위 상태로 열어놓고.

다음엔 워드패드나 사용하는 텍스트에디터를 실행시킨 후 안드로이드 팩키지가 설치된 폴더 밑에
Android\android-sdk\extras\google\usb-driver\android_winusb.inf 파일을 연다.

자 이 파일은 android-sdk 의 SDK Manager.exe 를 실행했을 때
Installed package 에 Google USB Driver Package, revision 3 이상이 설치되어 있어야 한다.
없으면 구글이나 여러 사이트에서 쉽게 설치할 수 있다.

자 에디터로 연 android_winusb.inf 파일을 보자

 


위의 붉은 글씨가 중요하다.
일단 [Goolgle.NTx86] 라는 카테고리가 있고 [Google.NTamd64] 라는 카테고리가 있다.
앞의 것은 32비트 OS (Window 98, XP, Vista) 용 카테고리이고
뒤의 것은 64비트 OS (Windows NT Server, Windows 7) 용 카테고리이다.

그 카테고리 명 바로 밑에 위의 붉은 부분 같이 네줄을 기록하는데.

;NOTE K : 주석으로 기기명을 넣든지 맘대로 넣으면 된다.
%SingleAdbInterface% = USB_Install, USB\VID_18d1&PID_0003&MI_01
%CompositeAdbInterface% = USB_Install,
USB\VID_18d1&PID_0003&REV_9999&MI_01
%SingleBootLoaderInterface% = USB_Install, USB\VID_0BB4&PID_0FFF

%SingleAdbInterface% 항목에는 USB_Install, 뒤에 아까 제어판에서 확인했던 Android 등록정보의
하드웨어 ID 값의 두번째 줄의 값을 기록하고
%CompositeAdbInterface% 항목에는 하드웨어 ID 값의 첫번째 줄의 값을 기록한다.
%SingleBootLoaderInterface% 는 위와 같이 기록하면 된다.

복잡하니까 원래 있었던
;HTC Dream 밑의 세줄을 복사해서 위에 붙혀놓고 붉은색 부분만 편집해도 된다.

자 편집한 이 네줄을 또 복사해서 아래에 있는 [Google.NTamd64] 에도 붙혀놓는다.
OS 가 32이건 64이건 그냥 그렇게 한다.

그리고 저장하고 편집기를 닫고
다시 아까 제어판장치관리자로가서 Android 등록정보 창에 가서 일반탭을 선택하고 '드라이버 다시 설치'를 선택한다.

-> 아니오, 지금 연결 안함 -> 다음
-> 목록 또는 특정 위치에서 설치(고급) -> 다음
-> 이 위치에서 가장 적합한 드라이버 검색을 선택하고 검색할 때 다음 위치 포함에 체크한 후 찾아보기

아까 편집한 android_iwnusb.inf 파일이 있는
Android\android-sdk\extras\google\usb-driver 폴더를 선택한다. 아래 처럼.

 


다음을 누르면 Android Composite ADB Interface 라는 기기로 설치가 시작된다.


 


성공적으로 설치를 완료한 상태이다.
때에 따라 이 상황에서 재 부팅을 요구하고 다시 드라이버를 설치하라고 할 수 있다.
그러면 재부팅하고 다시 위에서 처럼 드라이버를 재 설치하면 된다.

다시 장치관리자를 보면

 

위와 같이 Android Phone 아래 Android Composite ADB Interface 의 기기가 설치되어 있다.

이클립스를 열어 개발된 앱하나를 수행해 보면
앱이 컴파일 되고 기기에 설치되면서 작동되는 것을 볼 수 있다.

짝짝짝.. 성공!!

'Android > Tip&Tech' 카테고리의 다른 글

[펌]Preferences 관련 자료  (0) 2013.07.29
위젯 관련 ppt  (0) 2013.07.22
Button을 계속누를때 UI Update 하는 방법  (0) 2013.07.09
font 크기 참조하자  (0) 2013.07.03
[펌]App Widget 개발에 필요한 것들  (1) 2013.06.28

출처 :

http://blog.rochdev.com/2011/05/update-ui-when-holding-button.html#!/2011/05/update-ui-when-holding-button.html

 

I got this question recently so thought I would share one of the solutions.

What we want is simply to update the user interface when holding down a button.

The code should be pretty self-explanatory.

The first thing we do is to create a custom button and override events.

Then in the Activity we register a OnLongClickListener for the button where we add a runnable to the message queue.

When running this code you will see a button which will display a ticking counter (1,2,3 ..) when the button is held down.

screenshot

package com.rochdev.sample.longpress;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.TextView;

public class SampleLongpress extends Activity {

    private MyButton mMyButton;
    private TextView mTextView;

    private Handler mHandler;
    private boolean mDown;
    
    int mCounter = 0;
    
    public SampleLongpress () {
        mHandler = new Handler();
    }

    private final Runnable mRunnable = new Runnable() {
        public void run() {
            if (mDown) {
                if (mTextView != null) {
                    mTextView.setText(Integer.toString(mCounter));
                    mCounter++;
                }
                mHandler.postDelayed(this, 1000);
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mMyButton = (MyButton) findViewById(R.id.my_button);
        mMyButton.setOnLongClickListener(new OnLongClickListener() {            
            @Override
            public boolean onLongClick(View v) {
                mDown = true;
                mHandler.post(mRunnable);
                return true;
            }
        });
        mMyButton.setSampleLongpress(this);

        mTextView = (TextView) findViewById(R.id.down_up_tv);
    }

    public void cancelLongPress() {
        mDown = false;
        if (mTextView != null) {
            mTextView.setText("");
            mCounter = 0;
        }
    }
}


 

package com.rochdev.sample.longpress;

import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.Button;

public class MyButton extends Button {    
    
    private SampleLongpress sampleLongpress;

    public MyButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public MyButton(Context context) {
        super(context);
    }
    
    public void setSampleLongpress(SampleLongpress sl) {
        sampleLongpress = sl;
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        cancelLongpressIfRequired(event);
        return super.onTouchEvent(event);
    }
    
    @Override
    public boolean onTrackballEvent(MotionEvent event) {
        cancelLongpressIfRequired(event);
        return super.onTrackballEvent(event);
    }
    
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
                || (keyCode == KeyEvent.KEYCODE_ENTER)) {
            cancelLongpress();
        }
        return super.onKeyUp(keyCode, event);
    }
    
    private void cancelLongpressIfRequired(MotionEvent event) {
        if ((event.getAction() == MotionEvent.ACTION_CANCEL)
                || (event.getAction() == MotionEvent.ACTION_UP)) {
            cancelLongpress();
        }
    }

    private void cancelLongpress() {        
        if (sampleLongpress != null) {
            sampleLongpress.cancelLongPress();
        }
    }    
}


 

<com.rochdev.sample.longpress.MyButton android:id="@+id/my_button"
        android:layout_width="wrap_content" android:layout_weight="1"
        android:layout_height="wrap_content" android:text="Longpress"
        android:textStyle="bold" />

'Android > Tip&Tech' 카테고리의 다른 글

위젯 관련 ppt  (0) 2013.07.22
안드로이드 기기 드라이버 설치  (0) 2013.07.10
font 크기 참조하자  (0) 2013.07.03
[펌]App Widget 개발에 필요한 것들  (1) 2013.06.28
dialog style에서 actionbar 사용하기  (0) 2013.06.27

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.util.Config;
import android.util.TypedValue;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.KeyEvent;
import android.widget.Button;
import android.widget.TextView;

public class Calculator extends Activity {
    EventListener mListener = new EventListener();
    private CalculatorDisplay mDisplay;
    private Persist mPersist;
    private History mHistory;
    private Logic mLogic;
    private PanelSwitcher mPanelSwitcher;

    private static final int CMD_CLEAR_HISTORY  = 1;
    private static final int CMD_BASIC_PANEL    = 2;
    private static final int CMD_ADVANCED_PANEL = 3;

    private static final int HVGA_HEIGHT_PIXELS = 480;
    private static final int HVGA_WIDTH_PIXELS  = 320;

    static final int BASIC_PANEL    = 0;
    static final int ADVANCED_PANEL = 1;

    private static final String LOG_TAG = "Calculator";
    private static final boolean DEBUG  = false;
    private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        setContentView(R.layout.main);

        mPersist = new Persist(this);
        mHistory = mPersist.history;

        mDisplay = (CalculatorDisplay) findViewById(R.id.display);

        mLogic = new Logic(this, mHistory, mDisplay, (Button) findViewById(R.id.equal));
        HistoryAdapter historyAdapter = new HistoryAdapter(this, mHistory, mLogic);
        mHistory.setObserver(historyAdapter);
        View view;
        mPanelSwitcher = (PanelSwitcher) findViewById(R.id.panelswitch);
                                      
        mListener.setHandler(mLogic, mPanelSwitcher);

        mDisplay.setOnKeyListener(mListener);


        if ((view = findViewById(R.id.del)) != null) {
//            view.setOnClickListener(mListener);
            view.setOnLongClickListener(mListener);
        }
        /*
        if ((view = findViewById(R.id.clear)) != null) {
            view.setOnClickListener(mListener);
        }
        */

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        MenuItem item;
       
        item = menu.add(0, CMD_CLEAR_HISTORY, 0, R.string.clear_history);
        item.setIcon(R.drawable.clear_history);
       
        item = menu.add(0, CMD_ADVANCED_PANEL, 0, R.string.advanced);
        item.setIcon(R.drawable.advanced);
       
        item = menu.add(0, CMD_BASIC_PANEL, 0, R.string.basic);
        item.setIcon(R.drawable.simple);

        return true;
    }
   
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        menu.findItem(CMD_BASIC_PANEL).setVisible(mPanelSwitcher != null &&
                          mPanelSwitcher.getCurrentIndex() == ADVANCED_PANEL);
       
        menu.findItem(CMD_ADVANCED_PANEL).setVisible(mPanelSwitcher != null &&
                          mPanelSwitcher.getCurrentIndex() == BASIC_PANEL);
       
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case CMD_CLEAR_HISTORY:
            mHistory.clear();
            break;

        case CMD_BASIC_PANEL:
            if (mPanelSwitcher != null &&
                mPanelSwitcher.getCurrentIndex() == ADVANCED_PANEL) {
                mPanelSwitcher.moveRight();
            }
            break;

        case CMD_ADVANCED_PANEL:
            if (mPanelSwitcher != null &&
                mPanelSwitcher.getCurrentIndex() == BASIC_PANEL) {
                mPanelSwitcher.moveLeft();
            }
            break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onSaveInstanceState(Bundle icicle) {
        // as work-around for ClassCastException in TextView on restart
        // avoid calling superclass, to keep icicle empty
    }

    @Override
    public void onPause() {
        super.onPause();
        mLogic.updateHistory();
        mPersist.save();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent keyEvent) {
        if (keyCode == KeyEvent.KEYCODE_BACK
            && mPanelSwitcher.getCurrentIndex() == ADVANCED_PANEL) {
            mPanelSwitcher.moveRight();
            return true;
        } else {
            return super.onKeyDown(keyCode, keyEvent);
        }
    }

    static void log(String message) {
        if (LOG_ENABLED) {
            Log.v(LOG_TAG, message);
        }
    }

    /**
     * The font sizes in the layout files are specified for a HVGA display.
     * Adjust the font sizes accordingly if we are running on a different
     * display.
     */

    public void adjustFontSize(TextView view) {
        float fontPixelSize = view.getTextSize();
        Display display = getWindowManager().getDefaultDisplay();
        int h = Math.min(display.getWidth(), display.getHeight());
        float ratio = (float)h/HVGA_WIDTH_PIXELS;
        view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontPixelSize*ratio);
    }
}

App Widget 이란 Home Screen에 노출되는 miniature Application입니다.
보통 시계 어플등이 Widget 형태로 존재 하게 됩니다.

App Widget을 구현에 필요 한것들

A. 꼭 필요 한것들

1. AppWidgetProviderInfo object
- App Widget을 위한 MetaData, update frequecy 나 layout에 대한 정의.
- XML 에 정의 되어야함.
2. AppWidgetProvider
- Programming Interface
- AppWidget이 Update/Enabled/Disabled/Delete되었을때, BroadCast를 받는 Receiver역할을 한다.
3. View Layout
- AppWidget Design , Android Layout XML 로 정의한다.
B. 추가로 할 수 있는것
App Widget Configuration Activity
- AppWidget이 생성될때, User가 설정하게 할수 있는 Optional Feature

요약
Android Manifest에 AppWiget을 정의하고, AppWidgetProvider를 구현함으로써 Programming 하고,
Layout을 통해 App Widget을 디자인한다.

Manifest에 App Widget 선언하기.

앞서 언급한대로, AppWidget에는 AppWidgetProvider라는 BroadCast Receiver가 필요 합니다.

<receiver android:name="ExampleAppWidgetProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver>

  • AppWidget Broadcast Reciver 등록
  • MetaData Resource 설정

AppWidgetProviderInfo MetaData


res/xml/ folder에 <appwidget-provider /> 태그를 작성한 yourappwidget-info.xml을 추가합니다.

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dp"
android:minHeight="72dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure" >
</appwidget-provider>


 

  • minWidth/minHeight 설정
    • 사실상 width값대로 표기되는게 아니라, 현재의 Cell Size에 따라 자동 계산된다.
    • Cell Size는 Device별로 다르고, 같은 Device일때도, Vertical이냐 Horizontal에 따라서 또 다르다.
    • ( Number of Cells ) * 74 -2 의 계산식으로 계산하여 설정하도록 한다.
  • updatePeriodMillis 설정
  • layout 명시
    • AppWidget Layout XML
  • configure(optional)
    • 유저가 AppWidget 설정시 사용될 Activity

AppWidget Layout

AppWidget의 Layout은 기본적으로 Activity용 Layout과 다를 바가 없다.
다만, AppWidget은 RemoteView를 기반으로 하고 있기 때문에, 사용할 수 있는 종류에 제약이 있다.

사용가능한 Layout Classes

사용가능한 Widget Classes

AppWidgetProviderClass 구현하기

아래 소스는 appWidget을 Click했을 때, 특정 Activity를 띄우는 AppWidget의 예이다.

public class ExampleAppWidgetProvider extends AppWidgetProvider {

public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;

// Perform this loop procedure for each App Widget that belongs to this provider
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];

// Create an Intent to launch ExampleActivity
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

// Get the layout for the App Widget and attach an on-click listener to the button
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
views
.setOnClickPendingIntent(R.id.button, pendingIntent);

// Tell the AppWidgetManager to perform an update on the current App Widget
appWidgetManager
.updateAppWidget(appWidgetId, views);
}
}
}


AppWidget의 기본적인 목적은 주기적으로 화면을 구성해주서 보여주는것이다.
그럼으로 특별히 Enable/Disable를 처리할 이유가 없다면, 내가 해야 할일은 onUpdate() Method를 구현하는것 뿐이다.

이제 AppWidget을 띄울 수 있는 기본적인 개념은 알게 되었다.


<style name="PopupTheme" parent="android:Theme.Holo.Light.Dialog">
<item name="android:windowIsFloating">false</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowSoftInputMode">stateAlwaysHidden</item>
<item name="android:windowActionModeOverlay">true</item>
<item name="android:windowIsTranslucent">true</item>
</style>

public static void showAsPopup(Activity activity) {
//To show activity as dialog and dim the background, you need to declare android:theme="@style/PopupTheme" on for the chosen activity on the manifest
activity.requestWindowFeature(Window.FEATURE_ACTION_BAR);
activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND,
WindowManager.LayoutParams.FLAG_DIM_BEHIND);
LayoutParams params = activity.getWindow().getAttributes();
params.height = LayoutParams.FILL_PARENT;
params.width = 850; //fixed width
params.alpha = 1.0f;
params.dimAmount = 0.5f;
activity.getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
}

내부적으로 android:id를 이용을 하고 있어서 따로 커스트마이징을 해서 지정을 해줘야함..귀찮음..

 

package com.example.setupproject;

import android.content.Context;
import android.preference.SwitchPreference;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Checkable;
import android.widget.CompoundButton;
import android.widget.Switch;

public class MySwitchWidget extends SwitchPreference {
 
 private final Listener mListener = new Listener(); 
 
 private class Listener implements CompoundButton.OnCheckedChangeListener {
  @Override        
  public void onCheckedChanged(
    CompoundButton buttonView, boolean isChecked) {
    if (!callChangeListener(isChecked)) {
     // Listener didn't like it, change it back.
     // CompoundButton will make sure we don't recurse.
     buttonView.setChecked(!isChecked);
     return;
    }//if             
    MySwitchWidget.this.setChecked(isChecked);
   }//    
 }//    
 public MySwitchWidget(Context context) {
  super(context); 
  init();
 }

 public MySwitchWidget(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }

 public MySwitchWidget(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);  
  init();
 }
 private void init(){
  setWidgetLayoutResource(R.layout.preference_widget_switch);  
 }

 @Override
 public void setWidgetLayoutResource(int widgetLayoutResId) {
  // TODO Auto-generated method stub
  super.setWidgetLayoutResource(widgetLayoutResId);
 }

 @Override
 protected void onBindView(View view) {
  ViewGroup viewGroup= (ViewGroup)view;
  clearListenerInViewGroup(viewGroup);
  super.onBindView(view);
  
  View checkableView = view.findViewById(R.id.pref_switch);
  if (checkableView != null && checkableView instanceof Checkable) {
   ((Checkable) checkableView).setChecked(isChecked());
   if (checkableView instanceof Switch) {
    final Switch switchView = (Switch) checkableView;
    //switchView.setAccessibilityDelegate();
    switchView.setOnCheckedChangeListener(mListener);
   }
  }
  // TODO Auto-generated method stub
  super.onBindView(view);
 }
 
 private void clearListenerInViewGroup(ViewGroup viewGroup) {
  if (null == viewGroup) {
   return;
  }
  int count = viewGroup.getChildCount();
  for(int n = 0; n < count; ++n) {
   View childView = viewGroup.getChildAt(n);
   if(childView instanceof Switch) {
    final Switch switchView = (Switch) childView;
    switchView.setOnCheckedChangeListener(null);
    return;
   }else if (childView instanceof ViewGroup){
    ViewGroup childGroup = (ViewGroup)childView;
    clearListenerInViewGroup(childGroup);     
   }//else
  }
 }
 
 
 
}

출처 : http://lsit81.tistory.com/33

 

며칠전 BitmapFactory.Options.inPurgeable에 대한 내용을 올렸는데요.
BitmapFactory.Options에 대한 정리가 필요할 것 같아 주요 옵션에 대하여 정리를 해보았습니다.


1. Image Width, Height 정보만 가져오기.
: BitmapFactory.Options.inJustDecodeBounds = true
이렇게 사용할 경우 Image를 메모리로 로드하지 않은 상태로 Image의 width, height 정보만을 가져올 수 있습니다.

1
2
3
4
5
6
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile("[경로]", opts);
Log.d("bitmap", "image width = " + opts.outWidth
+ ", height = " + opts.outHeight + ", mime type = " + opts.outMimeType);

public boolean inJustDecodeBounds

If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.





2. 메모리 최적화하여 화면 크기에 맞게 Image Load하기.
안드로이드는 기본적으로 JVM Heap Memory가 얼마되지 않습니다.
구글에서는 최소 16MByte로 정하고 있으나, 제조사 별로 디바이스별로 Heap영역의 크기는 다르게 정하여 사용하고 있습니다.

그러나 제조사별로 좀더 많은 양의 heap 영역을 셋팅을 하였다 하여도 32Mbyte를 넘는 디바이스는 흔치 않으며, 많은 양의 그래픽 작업을 해야 하는 경우 heap 메모리는 여전히 부족합니다.

그래서 Image를 load할때는 필요한 만큼만 이미지를 sampling 하여 로드를 할 필요가 있습니다.

이를 지원해 주는 것이 BitmapFactory.Options.inSampleSize 옵션입니다.
구글 Developer Site에 가보시면 아래와 같이 정의가 되어 있습니다.

public int inSampleSize

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder will try to fulfill this request, but the resulting bitmap may have different dimensions that precisely what has been requested. Also, powers of 2 are often faster/easier for the decoder to honor.



그리고 아래와 같이 화면(또는 이미지 뷰) 크기에 따라서 이미지를 sampling하여 사용하면 더욱 효과적으로 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static Bitmap readImageWithSampling(String imagePath, int targetWidth, int targetHeight,
Bitmap.Config bmConfig) {
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, bmOptions);
int photoWidth = bmOptions.outWidth;
int photoHeight = bmOptions.outHeight;
// Determine how much to scale down the image
int scaleFactor = Math.min(photoWidth / targetWidth, photoHeight / targetHeight);
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inPreferredConfig = bmConfig;
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Bitmap orgImage = BitmapFactory.decodeFile(imagePath, bmOptions);
return image;
}



3. 빠르게 Image load하기?
: BitmapFactory.Options.inDither = false

inDither는 이미지를 깔끔하게 처리해주는 옵션이라고 하는데...
테스트해본 결과 제가 보기에는 false 옵션을 주는 것이 이미지는 더 이뻐 보이는 건 저만의 착각일까요???

참고로 이미지를 loading하는 속도 또한 false로 주었을 경우 이미지에 따라 최대 약 2배까지 빠른 것을 보았습니다.

아래는 테스트한 샘플 사진을 첨부합니다.
자세히 보시면 약간의 차이를 느끼실 수 있습니다.


위쪽 사진이 false 옵션을 주어 이미지를 로드한 상태고

아래 사진이 true 옵션을 주어 이미지를 로드한 상태 입니다.


이미지 로딩 시간은 각각 623ms, 542ms가 걸렸습니다.


 




4. Drawable-hdpi, mdpi 에 있는 리소스 이미지 크기 변환 없이 원본 load 하기.

안드로이드에서는 drawable에 있는 이미지를 load시 해당 dpi에 맞춰 이미지를 자동으로 resize하여 이미지를 로드하게 됩니다.
그런데 간혹 resize 없이 이미지를 로드하고 싶은 경우가 생기는데요..

그럴 경우 inScaled 옵션을 false로 설정하여 이미지를 불러오면 resize 없이 이미지를 불러올 수 있습니다.

public boolean inScaled

When this flag is set, if inDensity and inTargetDensity are not 0, the bitmap will be scaled to match inTargetDensity when loaded, rather than relying on the graphics system scaling it each time it is drawn to a Canvas.

This flag is turned on by default and should be turned off if you need a non-scaled version of the bitmap. Nine-patch bitmaps ignore this flag and are always scaled.

출처 : http://givenjazz.tistory.com/53

 

클래스파일: Create9Patch.class

안드로이드는 해상도가 다르다고 해도 같은 화면으로 보여주기 위해 이미지를 늘려주는 나인패치라는 포맷을 사용합니다. 주로 draw9patch라는 툴을 사용합니다만 이걸 만드는 작업이 여간 귀찮은 게 아닙니다. 9패치 만드는 게 너무 귀찮아서 간단히 만들었는데 생각보다 쓸만해서 공개합니다.

그냥 상하좌우 5픽셀 떨어진 곳부터 5픽셀짜리 나인패치를 그려주는 게 전부입니다.
그럼 어떻게 되느냐? 예제를 보면 더 이해가 빠르실 겁니다.


5픽셀짜리 영역을 만들면 위처럼 5픽을 자동으로 지정만 해줘도 그라데이션이 들어가 있는 버튼도 생각보다 깔끔하게 확대가 됩니다.

사용법은 이 글에 첨부된 Create9Patch.class를 다운받아서 java Create9Patch [대상 폴더 혹은 파일] 로 실행하면 됩니다. 대상을 파일이 아닌 폴더로 하게 되면 폴더에 있는 모든 png파일을 9.png형식으로 변경합니다.

곧 나올 씨네21 앱에 나인패치를 적용했을 떄 기기별 화면에 보여지는 모습입니다.

넥서스 S)


갤럭시 탭 7인치)


갤럭시탭 8.9인치)


소스는 git에 공개합니다.
https://github.com/givenjazz/Create9Patch

그리고 터미널창에서 좀 더 디테일하게 변경할 수 있는 툴도 github에 있더군요.
https://github.com/reimund/9-Patcher

퍼옴 : http://blog.naver.com/PostView.nhn?blogId=nimbusob&logNo=147715066&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView

 

앞선 포스트
Android 개발가이드 - Bitmap.recycle()은 가급적 호출해 주는 편이 좋다. 에서는 명시적인 오브젝트 해제의 필요성을 알아보았다. VM 을 사용하는 언어에서 코딩함에도, c 스타일의 리소스 해제를 왜 해주어야 하는지에 대한 내용이었다. (안드로이드 프레임웍의 문제 및 특수성, 한정적인 VM Heap size 와 같은 이유 등)

그러나 안드로이드의 내부 동작으로 인해 Bitmap.recycle() 과는 별개로 view 에서의 메모리 누수 위험은 항상 잠재해 있다(자세한 내용은 안드로이드 내부 구현에 대한 이야기로 이어져야 하므로 생략). 결국 가장 안전한 방법은, view 에 할당된 모든 것을 초기화 해 버리는 것이다.

그럼 어떻게 할 것인가? 화면에 보이는 수많은 뷰들에 대해 일일히 null을 할 것인가?
이 점에 착안하여 아래와 같은 지저분한 트릭을 고안해 보았다.
왜 지저분하냐면, 정리 과정에 어느 정도의 성능 저하가 우려되기 때문이다.
주요 성능저하의 원인은 반복되는 재귀 호출 및 Root View 부터의 완전 트리 탐색 로직 때문이다.
게다가 마지막의 System.gc() 콜은 설명이 필요없이 지저분하다. (자세한 내용은 API 문서 참고 http://developer.android.com/reference/java/lang/System.html#gc() - GC호출은 GC를 보장하지 않음)

그러므로 가벼운 앱에서는 이 내용은 고민하지 않아도 무방하다.
View 갯수가 많은 화면을 Tab 혹은 ViewFlipper 등으로 구현한 경우와 같이,
메모리가 절박한 경우라면 이 트릭이 도움이 될 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.ImageView;
/**
* Utility class to help unbinding resources consumed by Views in Activity.
*
* @author Hwan Jo(nimbusob@gmail.com)
*/
public class ViewUnbindHelper {
/**
* Removes the reference to the activity from every view in a view hierarchy
* (listeners, images etc.). This method should be called in the onDestroy() method
* of each activity.
* This code may stinks, but better than worse - suspiciously, Android framework
* does not free resources immediately which are consumed by Views and this leads to
* OutOfMemoryError sometimes although there are no user mistakes.
*
* @param view View to free from memory
*/
public static void unbindReferences(View view) {
try {
if (view != null) {
unbindViewReferences(view);
if (view instanceof ViewGroup) {
unbindViewGroupReferences((ViewGroup)view);
}
}
} catch (Exception ignore) {
/* whatever exception is thrown just ignore it because a crash is
* always worse than this method not doing what it's supposed to do
*/
}
}
/**
* Removes the reference to the activity from every view in a view hierarchy
* (listeners, images etc.). This method should be called in the onDestroy() method
* of each activity.
* This code may stinks, but better than worse - suspiciously, Android framework
* does not free resources immediately which are consumed by Views and this leads to
* OutOfMemoryError sometimes although there are no user mistakes.
*
* @param view View to free from memory
*/
public static void unbindReferences(Activity activity, int viewID) {
try {
View view = activity.findViewById(viewID);
if (view != null) {
unbindViewReferences(view);
if (view instanceof ViewGroup) {
unbindViewGroupReferences((ViewGroup)view);
}
}
} catch (Exception ignore) {
/* whatever exception is thrown just ignore it because a crash is
* always worse than this method not doing what it's supposed to do.
*/
}
}
private static void unbindViewGroupReferences(ViewGroup viewGroup) {
int nrOfChildren = viewGroup.getChildCount();
for (int i = 0; i < nrOfChildren; i++) {
View view = viewGroup.getChildAt(i);
unbindViewReferences(view);
if (view instanceof ViewGroup) {
unbindViewGroupReferences((ViewGroup)view);
}
}
try {
viewGroup.removeAllViews();
} catch (Exception ignore) {
// AdapterViews, ListViews and potentially other ViewGroups don't support the removeAllViews operation
}
}
private static void unbindViewReferences(View view) {
// Set everything to null (API Level 8)
try {
view.setOnClickListener(null);
} catch (Exception ignore) {}
try {
view.setOnCreateContextMenuListener(null);
} catch (Exception ignore) {}
try {
view.setOnFocusChangeListener(null);
} catch (Exception ignore) {}
try {
view.setOnKeyListener(null);
} catch (Exception ignore) {}
try {
view.setOnLongClickListener(null);
} catch (Exception ignore) {}
try {
view.setOnClickListener(null);
} catch (Exception ignore) {}
try {
view.setTouchDelegate(null);
} catch (Exception ignore) {}
Drawable d = view.getBackground();
if (d != null) {
try {
d.setCallback(null);
} catch (Exception ignore) {}
}
if (view instanceof ImageView) {
ImageView imageView = (ImageView)view;
d = imageView.getDrawable();
if (d != null) {
d.setCallback(null);
}
if (d instanceof BitmapDrawable) {
Bitmap bm = ((BitmapDrawable)d).getBitmap();
bm.recycle();
}
imageView.setImageDrawable(null);
} else if (view instanceof WebView) {
((WebView)view).destroyDrawingCache();
((WebView)view).destroy();
}
try {
view.setBackgroundDrawable(null);
} catch (Exception ignore) {}
try {
view.setAnimation(null);
} catch (Exception ignore) {}
try {
view.setContentDescription(null);
} catch (Exception ignore) {}
try {
view.setTag(null);
} catch (Exception ignore) {}
}
}

위 코드를 Activity 화면의 onDestroy 에서 호출해 주면 확실히 메모리 절약 효과가 있다. 우리가 미처 신경쓰지 못한 내부 callback 등까지 확실히 제거해 주므로 View 가 올바른 GC의 대상이 되기 때문이다

'Android > Tip&Tech' 카테고리의 다른 글

[펌]Android BitmapFactory.Options 설명  (0) 2013.06.18
[펌] 나인패치 자동 생성  (0) 2013.06.14
해상도 지원  (0) 2013.06.03
Android Samsung Spen Sdk API  (1) 2013.01.10
[펌] JSP 에서 엑셀 다운로드 받기  (0) 2012.11.12

+ Recent posts