블로그 이미지
엡뽀
피난(?) 오신걸 환영합니다.

calendar

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

Notice

2012. 12. 31. 17:20 Programing/Android


드디어 Android UI 만들어 보자의 마지막 포스팅이네요...;; 개인적으로 시간이 잘 안난것도 이유가 되겠지만 더 큰 이유는 귀찮니즘 때문에 너무 질질 끌어왔었네요;; (이 Androud UI 만들기 포스팅만 몇주를 하고있는 것같은;;;)


다시 한번 마음을 다잡으며 포스팅을 열심히 해볼렵니다. 아자아자!! ㅎㅎ



다시 본론으로 넘어와서, 이전 포스팅까지의 내용을 정리하여 보면, Activity를 두개 만들어서, 하나에선 기본 뷰들만 나타나고, 다른 하나에서는 리스트 뷰가 나타나도록 구성한뒤, 기본 뷰에서 Action bar를 통하여 리스트 뷰의 엑티비티가 실행되도록 프로젝트를 작성하였습니다. 


여기에 Tabbar를 추가하여 두개의 엑티비티로 되어있는 프로젝트를 하나의 엑티비티에서 두개의 뷰로 볼수있는 구조로 변경하도록 하겠습니다.  단 이 Tabbar도 오픈소스를 적용하여 진행하도록 하겠습니다. ( 처음 계획은 오픈 소스는 살며시 스쳐 지나가겠금 할려했었는데 자꾸만 오픈소스를 추가 하게 되네요;; github 도 쓸줄도 모르면서 ㅜㅜ)




▶android-viewflow 오픈소스

URL : https://github.com/pakerfeldt/android-viewflow

 




벌써 3번째 오픈 소스 사용이니 파일 복사 부분은 간략히만 짚고 넘어가겠습니다.




res\values\attrs.xml 내용 복사

    <declare-styleable name="ViewFlow">
        <attr name="sidebuffer" format="integer" />
    </declare-styleable>
    <declare-styleable name="CircleFlowIndicator">
        <attr name="activeColor" format="color" />
        <attr name="inactiveColor" format="color" />
        <attr name="radius" format="dimension" />
		<attr name="centered" format="boolean" />
		<attr name="fadeOut" format="integer" />
		<attr name="inactiveType">
			<flag name="stroke" value="0" />
			<flag name="fill" value="1" />
		</attr>
		<attr name="activeType">
			<flag name="stroke" value="0" />
			<flag name="fill" value="1" />
		</attr>
    </declare-styleable>    
    <declare-styleable name="TitleFlowIndicator">
        <attr name="titlePadding" format="dimension" />
        <!-- Left/right padding of not active view titles. -->
        <attr name="clipPadding" format="dimension" />
        <attr name="selectedColor" format="color" />
        <attr name="selectedBold" format="boolean" />
        <attr name="selectedSize" format="dimension" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="footerLineHeight" format="dimension" />
        <attr name="footerColor" format="color" />
        <attr name="footerTriangleHeight" format="dimension" />
        <attr name="customTypeface" format="string" />
    </declare-styleable>


프로젝트에 org.taptwo.android.widget 패키지 추가


src\org\taptwo\android\widget\ 파일들 전체 복사


p.s) 소스파일들을 추가 하시게 되면, 소스에 오류가 나타납니다. 패키지 명이 달라서 나타나는 증상이니, 

import org.taptwo.android.widget.viewflow.R; 이분을 제거 하고 다시 패키지명.R을 임포트 하시면 됩니다. (간단하게 이클립스에서 Ctrl + Shift + O 한번  하시면 됩니다.)




그럼 이것으로 오픈 소스를 사용할 준비는 완료되었으니, 기존의 소스들을 수정 하도록 하겠습니다.


1. activity_main.xml 파일 수정


- activity_main.xml -

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/blog.example.androidui"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    
    <com.markupartist.android.widget.ActionBar
        android:id="@+id/actionbar"
        style="@style/ActionBar" />

    
    <org.taptwo.android.widget.ViewFlow
        android:id="@+id/viewflow"
		android:duplicateParentState="true" 
		android:layout_width="fill_parent" 
		android:layout_height="0dip"
		android:layout_weight="1">
	    
	</org.taptwo.android.widget.ViewFlow>
     <View
        android:layout_width="fill_parent"
        android:layout_height="2dip"
        android:background="#FFFF0000"
        />
    <LinearLayout android:layout_width="fill_parent"
		android:gravity="center_horizontal" android:id="@+id/header_layout"
		android:orientation="vertical" 
		android:layout_height="wrap_content">
		
        <org.taptwo.android.widget.TitleFlowIndicator
			android:id="@+id/viewflowindic" 
			android:layout_height="48dip"
			android:layout_width="fill_parent"
			app:footerLineHeight="2dp"
			app:footerTriangleHeight="10dp" 
			app:textColor="#A0FF0000" 
			app:selectedColor="#FFFF0000" 
			app:footerColor="#FFFF0000" 
			app:titlePadding="10dp" 
			app:textSize="17dp" app:selectedSize="18dp" 
			android:layout_marginTop="10dip" 
			app:clipPadding="5dp">
		</org.taptwo.android.widget.TitleFlowIndicator>

	</LinearLayout>

</LinearLayout>


우선 Action Bar 아래에 나오던 기본 뷰들을 전부 지웠구요, 그 부분에 viewflow 와 TitleFlowIndicator가 나타나도록 수정하였고, 뷰들의 속성 값들을 수정하였습니다.

 

자 그런데 여기서 TitleFlowIndicator 속성을 보시게되면 app: 이라는 접두어가 붙은 속성들이 등장하게됩니다. 

이 android-viewflow 오픈 소스는 app 이라는 xml 네임스페이스를 추가하여 view들의 속성을 지정하고있는데요. 이에 따라 android-viewflow 의 객체를 포함하는 최상위 view는 (여기선 LinearLayout 가 되겠지요.)  'xmlns' 라는 속성값을 통해 app 이라는 네임스페이스를 아래와 같이 추가 해주어야 합니다. 


xmls:app = "http://schemas.android.com/apk/res/프로젝트 패키지명"


p.s) 그런데 네임스페이스라고 보는게 옳은건지 잘모르겠네요;; 그저 이해를 돕기 위해 쓴 말이라고 생각하시면 될 것같습니다. 




2. default.xml 파일 추가


-default.xml-

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_marginLeft="16dip"
        android:layout_marginRight="16dip"
        android:layout_marginTop="30dip"
        android:layout_weight="1"
        android:orientation="vertical" >

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dip"
            android:text="@string/session"
            android:textColor="#000000"
            android:textSize="22sp" />

        <View
            android:layout_width="fill_parent"
            android:layout_height="1dip"
            android:background="#000000" />

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="48dip"
            android:baselineAligned="true" >

            <EditText
                android:layout_width="0dip"
                android:layout_height="wrap_content"
                android:layout_marginBottom="4dip"
                android:layout_marginRight="4dip"
                android:layout_marginTop="4dip"
                android:layout_weight="1"
                android:hint="@string/hint" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="8dip"
                android:contentDescription="@string/modify"
                android:src="@drawable/content_edit" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="48dip" >

            <EditText
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="4dip"
                android:layout_marginRight="4dip"
                android:layout_marginTop="4dip"
                android:hint="@string/hint" />
        </LinearLayout>

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dip"
            android:text="@string/session"
            android:textColor="#000000"
            android:textSize="22sp" />

        <View
            android:layout_width="fill_parent"
            android:layout_height="1dip"
            android:background="#000000" />

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="48dip"
            android:baselineAligned="true" >

            <Spinner
                android:id="@+id/androidui_sp_1"
                android:layout_width="0dip"
                android:layout_height="wrap_content"
                android:layout_marginBottom="4dip"
                android:layout_marginRight="4dip"
                android:layout_marginTop="4dip"
                android:layout_weight="1" />

            <Spinner
                android:id="@+id/androidui_sp_2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="4dip"
                android:layout_marginLeft="4dip"
                android:layout_marginTop="4dip" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="48dip"
            android:baselineAligned="true" >

            <EditText
                android:layout_width="0dip"
                android:layout_height="wrap_content"
                android:layout_marginBottom="4dip"
                android:layout_marginRight="4dip"
                android:layout_marginTop="4dip"
                android:layout_weight="1"
                android:hint="@string/hint" />

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="4dip"
                android:layout_marginLeft="4dip"
                android:layout_marginTop="4dip"
                android:text="@string/send" />
        </LinearLayout>
    </LinearLayout>

</LinearLayout>


별거 없습니다. 그냥 기존 activity_main.xml 에있던 기존 뷰들로 새로운 xml 파일을 하나 더 만들었습니다. 



3. activity_list.xml 파일 수정

- activity_list.xml -

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    
    <com.markupartist.android.widget.PullToRefreshListView
        android:id="@+id/android:list"
        android:layout_height="fill_parent"
        android:layout_width="fill_parent"
        android:cacheColorHint ="#00000000"
        />
	
</LinearLayout>


이것도 별거 없습니다. 기존 소스에선 ActionBar와 Textview로 만들어진 네이게이션바를 빼고, 속성을 몇가지 수정한게 전부입니다. 



4. ViewFlowAdapter 구현


viewflow 클래스는 listview 처럼 Adapter를 통하여 데이터 및 뷰를 보여주고있습니다. 이러한 이유때문에 activity_main.xml 파일 외에 두개의 xml 파일이 추가적으로 필요하여, default.xml 파일을 새로 만들고, activity_list.xml 파일을 수정하였습니다. 



또한 TitleFlowIndicator 클래스는 TitleProvider 인터페이스를 구현한 클래스로 부터 데이터를 읽어들이는데 ViewFlowAdapter 클래스가 TitleProvider 인터페이스까지 구현하도록 하겠습니다.


- ViewFlowAdapter.java -

package blog.example.androidui;

import org.taptwo.android.widget.TitleProvider;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public class ViewFlowAdapter extends BaseAdapter implements TitleProvider {

	private LayoutInflater mInflater;
	
	private String[] titles = {"View1", "View2"};    //타이틀
	
	public ViewFlowAdapter(Context context){
		mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	}
	
	
	@Override
	public int getCount() {
		return titles.length;    //아이템 갯수.
	}

	@Override
	public Object getItem(int position) {
		return position;
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

         /* 포지션에 따라 생성하는 view를 다르게 설정 */
 
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		if(convertView == null){
			if(position == 0){	//기본뷰
				convertView = mInflater.inflate(R.layout.default_view, null);
				
			}else{	//리스트뷰
				convertView = mInflater.inflate(R.layout.activity_list, null);
			}
		}
		
		return convertView;
	}

	@Override
	public String getTitle(int position) {    //타이틀 값.
		return titles[position];
	}

}


ViewflowAdapter 클래스는 BaseAdapter를 상속받아 만든 클래스이기 때문에 특별히 따로 설명드릴만한건 없는것 같습니다. 

다만 getView 메소드에서 포지션값에 따라 view가 다르게 생성되는 부분만 유의 하시면 될것같습니다. 



4. MainActivity 수정


- MainActivity.java -

package blog.example.androidui;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Vector;

import org.taptwo.android.widget.TitleFlowIndicator;
import org.taptwo.android.widget.ViewFlow;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Spinner;

import com.markupartist.android.widget.ActionBar;
import com.markupartist.android.widget.ActionBar.Action;
import com.markupartist.android.widget.ActionBar.IntentAction;
import com.markupartist.android.widget.PullToRefreshListView;
import com.markupartist.android.widget.PullToRefreshListView.OnRefreshListener;

public class MainActivity extends Activity {

	private ViewFlow viewFlow;  //오픈 소스 객체.

	
	//listview 관련
	private PullToRefreshListView list = null;
	
	private String[] mStrings = {
            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam",
            "Abondance", "Ackawi", "Acorn", "Adelost", "Affidelice au Chablis",
            "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
            "Allgauer Emmentaler"};
	private LinkedList<String> mListItems;
	
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ActionBar actionBar = (ActionBar) findViewById(R.id.actionbar);
        actionBar.setTitle("Home");
        
        final Action shareAction = new IntentAction(this, createShareIntent(), R.drawable.ic_title_share_default);
        actionBar.addAction(shareAction);
        
        //ListActivity를 실행하는 Action 주석 처리.
        //Intent intent = new Intent(this, ListViewActivity.class);
        //intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        
        //final Action otherAction = new IntentAction(this, intent, R.drawable.ic_title_export_default);
        //actionBar.addAction(otherAction);  
        
        
        viewFlow = (ViewFlow) findViewById(R.id.viewflow);
        ViewFlowAdapter adapter = new ViewFlowAdapter(this); //adapter 객체 생성
        viewFlow.setAdapter(adapter); //adapter 설정
        
        TitleFlowIndicator indicator = (TitleFlowIndicator) findViewById(R.id.viewflowindic);
		indicator.setTitleProvider(adapter); // title provider 설정
		viewFlow.setFlowIndicator(indicator); //viewflow 객체에 titleflowindicator 설정.
        
	// 기존  MainActivity 소스  	
        Spinner sp1 = (Spinner)viewFlow.findViewById(R.id.androidui_sp_1);
        Spinner sp2 = (Spinner)viewFlow.findViewById(R.id.androidui_sp_2);
        
        Vector<String> data1 = new Vector<String>();
        Vector<String> data2 = new Vector<String>();
        
        data1.add("2011-12-25");
        data2.add("8:00 am");
        
        ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(
				this,
				android.R.layout.simple_spinner_item,
				data1);

        adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		sp1.setAdapter(adapter1);
		
		ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(
				this,
				android.R.layout.simple_spinner_item,
				data2);

		adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		sp2.setAdapter(adapter2);
        
		
		// 기존 ListActivity 소스 
		list = (PullToRefreshListView)viewFlow.findViewById(android.R.id.list); //  list의 id값이 android의 list 값이기 때문에 androi.R 을 참조.
		
		mListItems = new LinkedList<String>();
        mListItems.addAll(Arrays.asList(mStrings));
        
        ArrayAdapter<String> listadapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, mListItems);

        list.setAdapter(listadapter);
		
		
		list.setOnRefreshListener(new OnRefreshListener() {
            public void onRefresh() {
                // Do work to refresh the list here.
                new GetDataTask().execute();
            }
        });
    }

	private class GetDataTask extends AsyncTask<Void, Void, String[]> {

        protected String[] doInBackground(Void... params) {
            // Simulates a background job.
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                ;
            }
            return mStrings;
        }

        protected void onPostExecute(String[] result) {
            mListItems.addFirst("Added after refresh...");

            // Call onRefreshComplete when the list has been refreshed.
            if(list != null) list.onRefreshComplete();

            super.onPostExecute(result);
        }
    }
	
	
	/* If your min SDK version is < 8 you need to trigger the onConfigurationChanged in ViewFlow manually, like this */
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		if(viewFlow != null) viewFlow.onConfigurationChanged(newConfig);	//Activity 환경설정 변경 
		super.onConfigurationChanged(newConfig);
	}
	
    public static Intent createIntent(Context context) {
        Intent i = new Intent(context, MainActivity.class);
        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        return i;
    }
    
    private Intent createShareIntent() {
        final Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TEXT, "Shared from the ActionBar widget.");
        return Intent.createChooser(intent, "Share");
    }
}


실행 결과















기본 뷰에서 보내기 버튼 옆에있는 EditText를 선택하면 키보드가 올라오면서 선택된 EditText가 보이지 않는 버그가 있긴하지만 최종 목표를 달성하였기 때문에 쿨하게 무시하도록 하겠습니다. (나란 남자 이런남자)


여기까지 Android UI 만들기 포스팅이었구요, 무한 테클 및 질문 환영합니다. 감사합니다. 



AndroidUI - 최종.zip






posted by 엡뽀
2012. 12. 20. 13:17 Programing/Android

2. 리스트 View

이번엔 리스트 View를 만들어 볼껀데요. 별다른거 없이. STATE BAR, ACTION BAR, LISTVIEW, NAVIGATION BAR로 화면 구성을 하도록 하겠습니다. 


- ListViewActivity -


최종 프로젝트의 목적은 하나의 Activity 에 두개의 View를 만들어서 네비게이션 바를 통해 화면 전환이 일어나도록 하는 것이지만, 우선은 새로운 Activity를 만들어서 MainActivity에서 ACTION BAR -> ORDER ACTION 버튼으로 화면 전환이 일어나도록 하겠습니다. 반대로 ListActivity 에서는 홈버튼을 누르면 MainActivity로 되돌아가는(하지만 사실상 ActivityStack에 의하여 back키 한번만 눌러도 되죠 ㅎㅎ)식으로 구성하겠습니다. 


또한 기본 ListView를 그대로 사용하는 것보다 오픈소스를 이용하여 아래로 스크롤시 아이템이 추가 되는 리스트를 사용하여 화면 구성을 하도록 하겠습니다. 


▶ android-pulltorefresh 오픈소스.

 URL : https://github.com/johannilsson/android-pulltorefresh

(또 다시 오픈 소스를 프로젝트에 추가를 해야하는군요... 그렇습니다, 또 다시 복사 붙여넣기!!!)



파일 다운로드 및 압축 풀기.




다운로드를 풀게 되면, pulltorefresh폴더와 pulltorefreshexample 폴더가 나오게 되는데 이중 pulltorefresh 폴더 내용물들을 복사하여 프로젝트에 붙여넣기 하시면 됩니다. 



res\drawable



res\drawable-hdpi (전체 붙여넣기)



res\drawable-ldpi (전체 붙여넣기)



res\drawable-mdpi (전체 붙여넣기)



res\layout (전체 붙여넣기)



res\values

strings.xml 파일은 프로젝트에 존재하니깐 이번에도 내용 복사로 하겠습니다.



res\values\strings.xml

    <string name="pull_to_refresh_pull_label">Pull to refresh...</string>
    <string name="pull_to_refresh_release_label">Release to refresh...</string>
    <string name="pull_to_refresh_refreshing_label">Loading...</string>
    <string name="pull_to_refresh_tap_label">Tap to refresh...</string>



src\com\markupartist\android\widget 

(소스 파일은 기존에 추가 하였던 com.markupartist.android.widget 패키지에 넣으시면 됩니다.)


이것으로 android-pulltorefresh 오픈 소스를 사용할 준비가 다 되었구요. 새로운 Activity와 레이아웃 파일을 만들어 보도록 하겠습니다. 


- activity_list.xml -

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <com.markupartist.android.widget.ActionBar
        android:id="@+id/actionbar"
        style="@style/ActionBar" />

    <com.markupartist.android.widget.PullToRefreshListView
        android:id="@+id/android:list"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="48dip"
        android:background="#a0ff0000"
        android:gravity="center"
        android:text="@string/navigation"
        android:textColor="#000000"
        android:textSize="22sp" />

</LinearLayout>

레이아웃 파일에서 리니어레이아웃을 최상위로 잡고, actionbar, listview, textview를 추가하였습니다.
여기서 하나 짚고 넘어가야하는 부분이, com.markupartist.android.widget.PullToRefreshListView 뷰의 id 설정 부분을 보시면 

android:id = "@+id/android:list" 

로 되어있는데요. 이는 activity를 만들때 ListActivity 클래스를 상속받아 만들기 위해서 지켜야하는 약속(?) 같은 것입니다. ListActivity 클래스는 Listview를 꼭 포함해야하며, Listview의 id가 android:list 값을 가져야합니다. (Android Developers 참조

하지만 사실상 Activity로 만든 다음에 findViewByID 메소드를 이용해서 list 객체를 얻어와도 상관없지만, 오픈소스의 예제를 충실히 따르기 위해서 id값을 저렇게 설정 하도록 하겠습니다. 



- ListViewActivity.java -

public class ListViewActivity extends ListActivity {
	//리스트 아이템.
	private String[] mStrings = {
            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam",
            "Abondance", "Ackawi", "Acorn", "Adelost", "Affidelice au Chablis",
            "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
            "Allgauer Emmentaler"};
	private LinkedList<String> mListItems;    //adapter 데이터 객체.
	
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_list);
		
                 //action bar 객체 얻기. 
		final ActionBar actionBar = (ActionBar) findViewById(R.id.actionbar);
        actionBar.setTitle("ListView");  //action bar 타이틀 설정.
        
        //action bar에 홈 action 설정. 
        actionBar.setHomeAction(new IntentAction(this, MainActivity.createIntent(this), R.drawable.ic_title_home_default));
        actionBar.setDisplayHomeAsUpEnabled(true); //action bar에 홈 버튼 보이기
        
         //OnRefreshListener 리스너 설정.
        ((PullToRefreshListView) getListView()).setOnRefreshListener(new OnRefreshListener() {
            public void onRefresh() {
                // Do work to refresh the list here.
                new GetDataTask().execute(); //Task 실행.
            }
        });
        
        mListItems = new LinkedList<String>(); //adapter 데이터 객체 생성
        mListItems.addAll(Arrays.asList(mStrings)); //데이터 설정
        
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, mListItems); //adapter 생성. 

        setListAdapter(adapter); //리스트에 어뎁터 설정.
        
	}
	
	//데이터 추가 Task
	private class GetDataTask extends AsyncTask<Void, Void, String[]> {

        protected String[] doInBackground(Void... params) {
            // Simulates a background job.
            try {
                Thread.sleep(2000); //2초동안 프로세스 중지.
            } catch (InterruptedException e) {
                ;
            }
            return mStrings; //onPostExecute 함수에 넘겨줄 데이터 리턴.
        }

        protected void onPostExecute(String[] result) {
            mListItems.addFirst("Added after refresh..."); //"Added after refresh..." 데이터 추가
            // Call onRefreshComplete when the list has been refreshed.
            ((PullToRefreshListView) getListView()).onRefreshComplete(); //변경 완료.

            super.onPostExecute(result);
        }
    }

}



ListViewActivity 코드에선 두가지 정도만 짚고 넘어가면 될 것 같습니다. 홈 action과 listview. 


1. action bar의 홈 action 

홈 action을 설정하는 코드를 보게 되면, 

actionBar.setHomeAction(new IntentAction(this, MainActivity.createIntent(this), R.drawable.ic_title_home_default));

로 되어있는데요. IntentAction 이라는 객체는 Context, intent, int를 매개변수로 받는 생성자를 가지고있습니다. 여기서 intent를 넘겨줄때 MainActivity의 static 메소드를 호출하여, 인텐트를 넘겨주고있습니다.

- MainActivity.java -

    public static Intent createIntent(Context context) {
        Intent i = new Intent(context, MainActivity.class);
        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        return i;
    }


자 그럼 여기서, 홈 acion, 즉 홈키를 누르면 뒤로 가야하는데, 왜 인텐트를 생성해서 넘겨주느냐? 하는 문제점이있는데요. 여기에서 제가 짚고 넘어가고 싶었던 인텐트의  속성(?)이 키워드입니다.

만약 소스코드에서 intent에 addFilags 를 하지않았다고 가정하게되면, ActivityStack이 아래와 같이 될것입니다. 

(출처 : 휴휴휴님 블로그)

즉, 뒤로 가기와 같은 효과가 아니라 새로운 Activity가 하나더 생성되게 되는거죠. 이러한 문제점을 intent의 flag로 해결할 수 있습니다. 


아래는 intent에 FLAG_ACTIVITY_CLEAR_TOP이라는 FLAG를 사용한 ActivityStack입니다. 



FLAG_ACTIVITY_CLEAR_TOP 플레그는 실행하고자 하는 Acitivity가 존재할 경우, 해당 Activity 위에있는 모든 activity를 종료시켜 줍니다. 따라서 홈으로 돌아가는 듯한 효과를 받게 되는 거죠. 이 FLAG 외에도 여러가지 FLAG가 존재 하는데요. 휴휴휴님의 블로그에서 자세히 소개 되고있습니다. (안드로이드 알아두면 요긴한 FLAG_ACTIVITY 네가지 :: http://huewu.blog.me/110087045138?Redirect=Log&from=postView )


2. android-pulltorefresh

오픈소스의 listview  "setOnRefreshListener"라는 메소드를 통해서 유저가 새로고침을 하게 될 경우 해야할 작업을 실행 할 수 있도록 해줍니다. 하지만 여기서 OnRefreshListener 리스너는 실행완료까지 자동으로 해주는 것은 아닙니다. 실제 작업이 완료된 이후에. listview의 onRefreshComplete() 메소드를 호출해주어야하죠.

이 소스코드에선 setOnRefreshListener 메소드를 통해 GetDataTask 를 실행하는 OnRefreshListener 를 구현한 임시 객체를 넘겨주고, 사용자가 새로고침(스크롤이 맨위로 올라가있는 상황에서 아래로 내렸을때) GetDataTask를 실행합니다.

GetDataTask 클래스는 AsyncTask 클래스를 상속받은 객체이며, doInBackground 메소드에선 하는 일없이, 2초간 쓰레드를 멈추후 작업을 종료합니다. 작업이 종료되면 onPostExecute 메소드가 호출되게 되는데 이때 adapter에서 사용하는 객체의 맨 처음 부분에 "Added after refresh..."라는 데이터만 추가한뒤에 listview의 onRefreshComplete() 메소드를 호출합니다.


여기까지가 두번째 Acitivity를 생성하여 호출하는 작업입니다. 마지막으로 Activity를 추가하였으니, 매니페스트 파일을 수정하여줍니다. 


- AndroidManifest.xml -

    <activity
            android:name=".ListViewActivity"
            android:label="@string/title_activity_list" >
    </activity>


(위 부분을 application 태그 안에다 추가 해주시면 됩니다. )


여기까지가, 기본뷰를 만들고, Activity를 추가하여 listview를 만드는 과정이었습니다. 여기까지 된 전체 소스를 올려드리도록 하겠으며, 다음 포스팅에선 두개의 Activity로 되어있는 구성을 하나의 Activity로 구성하면서, Tabbar로 두개의 화면을 보여줄수 있도록 수정하는 작업을 하도록 하겠습니다. 


현재 까지 작업된 전체 소스 파일.

AndroidUI.zip







posted by 엡뽀
2012. 11. 29. 16:50 Programing/Android


1. 기본 View 만들기.

기본 View를 만들기 전에 Example 그림을 다시 한번 보면서 어떤 어떤 View들이 들어가있느지 부터 확인을 해 보겠습니다. 


Example


STATUS BAR, ACTION BAR, TextView, EditText, ImageView(혹은 ImageButton), Spinner, Button NAVIGATION BAR
구성이 되어있네요. 


여기서 STATUS BAR, ACTION BAR는 API 4.0이상으로 빌드 타겟으로 프로젝트를 생성한다면 다른 레이아웃 수정없이 자동적으로 구성이 됩니다. 하지만 여기서 짚고 넘어가야할게 사실상 4.0 이상 버젼보다 2.2(프로요)와 2.3(진져브레드) 가 더 많지요.


VersionCodenameAPI

Distri

1.5Cupcake30.1%
1.6Donut40.3%
2.1Eclair73.1%
2.2Froyo812%
2.3 - 2.3.2Gingerbread90.3%
2.3.3 - 2.3.71053.9%
3.1Honeycomb120.4%
3.2131.4%
4.0.3 - 4.0.4Ice Cream Sandwich1525.8%
4.1Jelly Bean162.7%







(출처 : http://developer.android.com/about/dashboards/index.html)


물론 앱의 빌드 타겟 대상은 개발자 마음대로라 하시고 싶으신 대로 하면됩니만, 제 개인적인 판단으로는 4.0이상으로 해서 현재 약 65%되는 사용자들을 놓치는 것보단 2.2로 빌드하여 많은 사용자들이 이용 할 수 있도록 하는 주의 입니다. 

그럼 여기서 ACTION BAR가 2.2에선 사용 할 수 없는 문제가 발생합니다. 그냥 텍스트 뷰를 ACTION BAR의 크기처럼 만들어도 상관은 없겠지만,  ACTION BAR를 하위 버젼에서도 사용할 수 있는 Open source가 있습니다.


* android-actionbar Open source

URL : https://github.com/johannilsson/android-actionbar



이렇듯 오픈 소스로 이클립스에서 git 연동하여 자신의 프로젝트에 해당 소스를 첨부하여 사용가능한듯 한듯 하나 제가 아직 사용할 줄 몰라서 ㅜㅜ (차후에 공부를 해서;; 이클립스와 git 연동 관련해서 포스팅을 하도록 하겠습니다.ㅜㅜ)


그리하여 제가 한 방법은 소스파일을 다운로드해서 그냥 프로젝트에다 붙여넣기!! ㅎㅎㅎㅎㅎ
우선 AndroidUI 라는 프로젝트를 생성한 후에 다운로드 한 소스파일을 붙여넣는거죠. (프로젝트 생성은 다들 아실꺼라 믿고 그냥 넘어가겠습니다. ㅎㅎ)


76 commits 라고 되어있는 윗부분에 보시면 Downloads 라는 부분이있습니다. 해당 페이지로 가시면 zip 파일 또는 tar.gz 파일로 다운을 받을 수가 있는데, zip 파일을 다운 받은 후 압축풀어서 res에있는 레이아웃 파일이든, 이미지 파일이든 혹은 소스 파일이든 전부 그냥 복사해서 제 프로젝트에다가 추가하겠습니다.





압축을 푸시면 actionbar 폴더와 actionbarexample 폴더가 나옵니다. actionbar 폴더가 오픈 소스라고 보시면 되고, actionbarexample 폴더가 해당 오픈소스로 만든 프로젝트라고 보시면 됩니다. 




actionbar 폴더안에는 res 폴더,src 폴더 및 매니페스트 파일과 몇가지 파일이 나오는데요, 제가 필요한건 소스파일과 res안에있는 파일들이니 다른건 신경쓰고 붙여넣기 해야할 파일들만 보여드릴께요. (해당 파일들을 프로젝트의 같은 이름의 폴더에 넣으시면 됩니다.)



res\drawable



res\drawable-hdip



res\layout


res\values (strings.xml, styles.xml 두 파일은 기본프로젝트에 포함되어있어서 파일 복사가 아니라 내용 복사로 하였습니다.)



string.xml

Failed to open intent...



style.xml








src\com\markupartist\android\widget (소스는 com.markupartist.android.widget 패키지를 새로 만드신 후에 붙여 넣기 하시면 됩니다.)



자 드디어 android-actionbar 소스를 사용할 준비다 다되었군요. (진짜 드디어;;) 그럼 이제 다시 프로젝트로 넘어와서 메인레이아웃 소스를 만들어보도록 하겠습니다.



    
    

    

        
        

        
            
        
		

            
            

            
            
        

        

            
            
        

        
        

        
        

        

            
            

            
            
        

        

            
            

            
        
    

    
        
    


(코드하이라이터가 왜 이렇게 나타나는지 모르겟네요;; 이클립스에 붙여넣기 하신담에 Ctrl + Shift + F :: 소스 정리 단축키 누르셔야할듯;;)

그럼 몇가지 부분만 짚고 넘어가도록 하겠습니다. 

(1) 우선 오픈소스의 커스텀 뷰입니다.



com.markupartist.android.widget.actionbar 라는 태그가 있는 이 태그는 현제 프로젝트의 특정 소스를 xml 파일에서 뷰로 만들어주는 커스텀 태그라고 보시면 됩니다. 해당 소스를 붙여넣기 할때 com.markupartist.android.widget라는 패키지를 만든후에 복사를 한다고 말씀을 드렸었는데요. 혹시나 다른 패키지의 이름으로 하신분이있다면 이부분을 변경해야하며, res\layout에 붙여넣은 xml파일 내용물에 들어가있는 패키지 명도 바꾸어 주어야 합니다. 


(2) view



Session이라는 글자밑에있는 밑줄을 어떻게 구현할까하다가 그냥 view에다가 배경화면으로 검은색을 준다음 view의 높이를 1dip만 줘서 라인처럼 보이게 구현하였습니다. (이 외에 방법론 Shape를 만들어서 추가하면 될것같은데 이것과 관련해선 다음에 기회 봐서 포스팅을 하도록 하겠습니다. :: 점점 일이 많아 지는 느낌이네요;; ㅎㅎ;;)


(3) Navigation Bar


    


Navigation Bar는 기본 View와 리스트 View 둘다 완성이 된 이후에 또 오픈 소스를 사용하여 구성을 할 예정이라 단순하게 TextView를 만들어서 배치하여보았습니다. 그 외적인 다른 뷰들은 단순한 마진 값들만 준거라 찬찬히 살펴보시면 될 것이라 믿고, 소스부분으로 넘어가도록 하겠습니다.


(4)MainActivity.java




public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);	
        
        //ActionBar 객체 얻기
        final ActionBar actionBar = (ActionBar) findViewById(R.id.actionbar);
        actionBar.setTitle("Home"); //액션바에 타이틀 설정
        
        //공유 엑션 객체 생성
        final Action shareAction = new IntentAction(this, createShareIntent(), R.drawable.ic_title_share_default);
        actionBar.addAction(shareAction); //공유 엑션을 엑션바에 추가
        
        //새로운 엑션 추가
        //final Action otherAction = new IntentAction(this, new Intent(this, ListViewActivity.class), R.drawable.ic_title_export_default);
        //actionBar.addAction(otherAction);
        
        //Spinner 설정
        Spinner sp1 = (Spinner)findViewById(R.id.androidui_sp_1);
        Spinner sp2 = (Spinner)findViewById(R.id.androidui_sp_2);
        
        Vector data1 = new Vector();
        Vector data2 = new Vector();
        
        data1.add("2011-12-25");
        data2.add("8:00 am");
        
        ArrayAdapter adapter1 = new ArrayAdapter(
				this,
				android.R.layout.simple_spinner_item,
				data1);

        adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		sp1.setAdapter(adapter1);
		
		ArrayAdapter adapter2 = new ArrayAdapter(
				this,
				android.R.layout.simple_spinner_item,
				data2);

		adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		sp2.setAdapter(adapter2);
        
    }

    
    public static Intent createIntent(Context context) {
        Intent i = new Intent(context, MainActivity.class);
        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        return i;
    }
    
    
    //공유 엑션 인텐트 만드는 메소드
    private Intent createShareIntent() {
        final Intent intent = new Intent(Intent.ACTION_SEND);	//묵시적 인텐트 선언
        intent.setType("text/plain");	//인텐트 타입지정
        intent.putExtra(Intent.EXTRA_TEXT, "Shared from the ActionBar widget.");	//인텐트 데이터 설정 
        return Intent.createChooser(intent, "Share");	//인텐트 리턴
    }
}
소스도 뭐 그다지 어려운게 없습니다. 그냥 findviewById 로 action 객체를 얻어오고, 타이틀 설정 및 버튼을 추가하는겁니다. createIntent 메소드는 쫌 짚고 넘어가고 싶은데.... 이에 대해서는 차후 ListViewActivity 클래스를 다 만들고 난 이후에 설명을 하도록 하겠습니다. (어짜피 MainActivity에선 만들기만 했지 사용하는 곳이 없어서 대략 난감하네요 ㅎㅎ)


이렇게 하여 실행한 화면입니다.





프로젝트 전체 파일은 ListView까지 작성한 후에 올리도록 하겠습니다. (이미 다 만들어 놓고 압축시켜놔서 따로 분리하기가 귀찮....)

P.S 혹시나 main layout 부분에서 에러가 나시는 분들은 string.xml에 제가 추가했던 텍스트가 추가 되지 않아서 그런거 일수 있으니, 그냥 text = "섹션" 과 같은 방식으로 변경하신 후 실행 해보세요.


posted by 엡뽀
2012. 11. 28. 16:34 Programing/Android

Design::Style > Typography (원본 : Android Developers)


Typography



안드로이드 디자인 언어는 기본 그리드와 규모, 공간, 리듬 그리고 정렬과 같은 전통적인 typography에 의존 하고있습니다. 사용자의 화면에 나타난 정보를 신속하게 이해하기 위해선 이러한 도구들의 성공적인 전개는 필수적입니다. typography의 이러한 사용을 지원하기 위해 IceSandwich는 UI 고해상도 요구를 위해 특별히 만들어진 Roboto type family를 도입하였습니다. 현재 TextView의 프레임 워크는 regular, bold, italic 과 bold italic weights를 기본적으로 지원합니다.




Default type colors

안드로이드 UI 아래와 같은 기본색들을 사용합니다.
textColorPrimarytextColorSecondary

밝은 테마의 UI는 아래와 같은 색을 사용 합니다.
textColorPrimaryInversetextColorSecondaryInverse.

프레임워크 텍스트 컬러 스타일 또한 터치 피드벡에 대한 상태  변경을 지원합니다. 


Typographic Scale


텍스트 사이즈 비교는 이해하기 쉬운 레이아웃을 만드는 것보다 훨씬 생상적입니다. 그러나, 한 UI에 다른 사이즈가 많이 들어간다면 지저분해질 수 있습니다. 안드로이드 프레임워크는 아래와 같은 몇가지 크기들만 사용합니다.


사용자는 환경설정 앱에서 시스템 전체 텍스트 크기를 선택 할 수 있습니다. 이러한 기능을 지원하기 위해 가능한한 텍스트 크기는 scale-independent pixels(SP)단위를 사용해야 합니다. 레이아웃은 이러한 설정에 대해 크기 변경이 가능한 텍스트를 지원해야합니다.












posted by 엡뽀
2012. 11. 28. 13:45 Programing/Android


Design::Style > Metrics and Grid (원본 : Android Developers)


Metrics and Grids


단말기들은 다양한 실제 기기 사이즈뿐만이 아니라 다양한 화면 밀도(DIP)를 가지고있습니다.
여러 화면을 위한 디자인을 간단히 하는 방법은 각각의 장치에 대해 물리사이즈와 논리사이즈를 별도로 생각하는 것입니다. 물리 사이즈는 handset(600dp 이하)와 table(600dp 이상)입니다. 논리사이즈는 LDPI, MDPI, HDPI와 XHDPI 입니다. 당신의 앱의 UI 최적화는 서로 다른 물리사이즈를 대체하는 레이아웃 설계와 각각의 논리 사이즈에 대응하는 bitmap 이미지를 제공하는 것입니다.

(추가 의견: 앱 레이아웃은 dip/dp 단위를 사용 하여 설계 또는 wrap_content, fill_parent로 어떤 물리사이즈이든 동일 하게 적용될 수 있도록 디자인하고, 앱에서 사용하는 이미지들은 각각의 LDIP/MDIP/HDIP/XHDIP 에 맞는 크기의 이미지를 넣어두라는 것입니다. :: LDIP : 36 * 36 / MDIP : 48 * 48/ HDIP : 72 * 72) 



Space considerations

단말기들은 매우 다양한 논리적 단위(dip)로 출력될 수 있습니다.
쫌 더 많은 정보를 볼려면 클릭하여 주세요. Screen Sizes and Densities Device Dashboard. (해당 부분은 번역을 올리지 않겠습니다.)


48dp Rhythm


일반적으로 UI 구성요소들은 48dip 단위에 따라 배치 되어있습니다.


Why 48dp?

평균적으로, 48dp는 약 9mm(변경 될 수 있음)로 환산 될 수 있습니다. 이 9mm는 터치 스크린 객체의 권장 되는 객체 사이즈이며, 사용자가 안정적으로 정확하게 손가락으로 타겟팅 할수 있는 편리한 사이즈입니다.

만약 요소를 48dp 높이와 폭으로 디자인 한경우 아래와 같이 보장할수 있습니다.

  • 당신의 요소가 어떤 장치에 보여기게 되더라고 최소 권장 크기인 7mm보다 작아선 안됩니다.
  • 당신은 전체적인 정보 요약과 UI 요소의 타겟팅을 적절히 공략하여야 합니다.


Mind the gaps


UI 요소들의 간격은 8dp 입니다.

Examples






'Programing > Android' 카테고리의 다른 글

Android UI를 만들어 보자 [1] - 기본 View 만들기-  (0) 2012.11.29
Android UI를 만들어 보자 [0]  (3) 2012.11.29
Android Design [3] -Gestures-  (0) 2012.11.28
Android Design [2] -Typography-  (0) 2012.11.28
Android Design [0]  (0) 2012.11.28
posted by 엡뽀
2012. 11. 28. 10:30 Programing/Android

안녕하세요. 블로거 엡뽀라고 합니다.


Android 블로그의 첫번째 포스트!  게다가 Android 카테고리를 먼저 다 포스트 하고 차례 차례 iPhone, Web 도 포스트 할 예정이라, 프로그래밍 카테고리에서의 첫 포스트인데요. 첫 단추를 잘 끼워야 하는 것처럼 맨 첨 포스팅을 어떤 주제로 할까에 대한 고민을 많이 했습니다.


그러다 블로깅 포스트 하는 프로그래밍의 기준을 정하고, 그 기준의 시발점 부터 포스팅을 하면되겠구나 싶었습니다. 그 기준이란 Android에서 제공하는 기본 widget (Android 바탕화면에서 보이는 그 위젯이 아닙니다.) TextView, EditText, ListView 등등 기본 View를 사용가능한 정도를 말합니다.


그리하여 기본 View들을 사용하는 방법을 알았으니, 어떻게 하면 잘 꾸밀수 있을까 하는 점이 첫 시작점인것 같아 Android Developers  래퍼런트 싸이트에서 소개 하고있는 디자인 방법에 대한 번역을 올릴까 합니다.


래퍼런트 싸이트에선 많은 내용을 다루고 있지만 제가 번역할 정도면 다른 분들도 충분히 할 수있을 거라는 가정과 래퍼런트 싸이트에서 직적 보시는게 아무래도 더 좋을 것같아 제가 생각 했을때 중요한 것같은 몇 가지만 번역을 올릴까 합니다. 


Design::Style > Metrics and Grid

Design::Style > Typography

Design::Patterns > Gestures


이에 대한 상세 포스팅은 다음 포스팅으로 하도록 하겠습니다. (글 수를 늘려볼려는 얄팍한 수작)

P.S) 구글 번역기의 도움을 받아 발 번역한거라 무한 테클 환영합니다.


posted by 엡뽀
prev 1 next