안드로이드 시스템은 date format과 time을 제공하고있는데요.

 

문제는 국가별로 date format이 조금씩 달라서 글로벌 서비스를 하는경우에는 조금은 고민을 해봐야합니다.

 

어떻게 적절하게 국가별로 date와 time을 보여줄지요.

 

예로들면 우리나라는 2월24일을 2.24 이런format으로 표현한다면

 

독일은 24.02. 프랑스는 24/02 등 조금씩 선호하는 방식이 다른편입니다.

 

국가별로 분기해서 작성하자니 만만지않을것같아서 찾아보니 웬걸? 안드로이드에서는 편이상 이런 기능들까지 다 제공해주고있습니다.

 

 

위의 그림은 epoch time으로 한국, 미국, 독일, 프랑스의 date format을 나타내본것이고 

time format도 출력해보았습니다. time format의 경우 현재 사용자가 12시간 format을 사용하고있는지 24시간 format을 사용하고있는지에 따라 출력되게끔 구현하였습니다.

 

 

 

MainActivity.java

main에서만 다 구성하였습니다.

 

getBestDateTimePattern - 현지 Locale에 맞는 date를 출력해줍니다. 기호에 따라 시간과 분도 format에 출력할 수 있습니다. 여기서 좋고 편한것은, MM과 DD의 위치를 신경쓰실 필요가없습니다.

Locale에 맞도록 적절하게 순서를 다시 바꿔서 return해줍니다.

 

getProperTimeAsClockPreference - 현재의 context 정보에 맞게끔 time format을 return하게 됩니다.

12시간, 24시간제 인지에 따라 변환해주고 또한, 국가에 따라 조금씩 다른 format을 주기까지합니다.

    public String getBestDateTimePattern (Locale locale) {
        //return  DateFormat.getBestDateTimePattern(locale, "MM dd hh:mm");
        return  DateFormat.getBestDateTimePattern(locale, "MM dd");
    }
    /*
    12 / 24 For distinguish
    static DateFormat	getTimeFormat(Context context)
    Returns a DateFormat object that can format the time according to the context's locale and the user's 12-/24-hour clock preference.
    */
    public java.text.DateFormat getProperTimeAsClockPreference(Context context) {
        return  DateFormat.getTimeFormat(context);
    }

 

 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);
        final TextView tv = findViewById(R.id.textView);

        button.setOnClickListener(new View.OnClickListener () {
            @Override
            public void onClick(View view) {

                SimpleDateFormat dateFormat = null;
                long time = System.currentTimeMillis();

                tv.append("\n time:::" + time);

                String bestDateFormat = getBestDateTimePattern(Locale.KOREA);
                dateFormat = new SimpleDateFormat(bestDateFormat);
                String convertedKoreaDate = dateFormat.format(new Date(time));

                tv.append("\n" + convertedKoreaDate);

                bestDateFormat = getBestDateTimePattern(Locale.US);
                dateFormat = new SimpleDateFormat(bestDateFormat);
                String convertedDate = dateFormat.format(new Date(time));

                tv.append("\n" + convertedDate);


                bestDateFormat = getBestDateTimePattern(Locale.GERMAN);
                dateFormat = new SimpleDateFormat(bestDateFormat);
                convertedDate = dateFormat.format(new Date(time));

                tv.append("\n" + convertedDate);

                bestDateFormat = getBestDateTimePattern(Locale.FRANCE);
                dateFormat = new SimpleDateFormat(bestDateFormat);
                convertedDate = dateFormat.format(new Date(time));

                tv.append("\n" + convertedDate);


                long stampAsCal;
                java.text.DateFormat formatDateTime;
                formatDateTime = getProperTimeAsClockPreference(getApplicationContext());
                String _time = formatDateTime.format(time);

                tv.append("\n converted time:" + _time);
                //kr 24 case: 1:11
                //kr 12 case: 오전 1:11

                //ge 24 case: 1:11
                //ge 12 case: 1:11 vorm

                // remove space
                String convertedConvertedKoreaDate = convertedKoreaDate.replace(" ", "");

                // remove . end of the date
                if (convertedConvertedKoreaDate.length() > 0 && convertedConvertedKoreaDate.charAt(convertedConvertedKoreaDate.length()-1) == '.') {
                    convertedConvertedKoreaDate = convertedConvertedKoreaDate.substring(0, convertedConvertedKoreaDate.length()-1);
                    Log.d("jinss", "gotta");
                }

                convertedConvertedKoreaDate = convertedConvertedKoreaDate + " " + _time;

                tv.append("\n final converted time:" + convertedConvertedKoreaDate);
            }
        });


    }

한국, 미국, 독일, 프랑스에 대해서 시험적으로 테스트 해보았습니다.

또한 최종 date와 time까지 출력도 해보았습니다.

 

앱을 실행해놓고, 12시간 24시간 변경도 해보신다음 버튼을 눌러서 텍스트가 바뀌어서 나오는지도 한번 확인해보시면 되겠습니다.

 

최신 안드로이드 버전 (P OS이후?) 부터는 List view보다는 Recycler View를 사용하는게

구글에서도 권고되는 사항인데요.

 

Recycler view를 다루는게 매번 헷갈리고 어렵고 힘든것같아서 이참에 뼈대코드도 작성해보고

포스팅하기로 마음먹었습니다.

 

 

바쁘신분들은 아래 링크를 눌러서 소스코드를 바로확인해보시기 바랍니다.

https://github.com/control-man/android-simple-sample/tree/master/recycler_view_simple_exam

 

 

Recycler view 구현 예제 (Cow를 눌러서 Toast 가 출력되고 있음)

 

1. res/layout/activitiy_main.xml 작성

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
        android:id="@+id/rvAnimals"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

Recycler view를 생성해줍니다.

 

2. res/layout/recycler_row.xml 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp"
    android:background="?android:attr/selectableItemBackground">
 
    <TextView
        android:id="@+id/tvAnimalName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20dp"/>
</LinearLayout>
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

Recycler view에서 각각의 item에 대한 view를 만들어줍니다.

여기선 LinearLayout안에 Textview한개를 두는 방식으로 배치할 것입니다.

 

 

3. MainActivity.java

MainActivity안에서 inner class인  MyRecyclerViewAdapter 를 정의합니다.

 

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
public static class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
 
        private List<String> mData;
        private LayoutInflater mInflater;
        private ItemClickListener mClickListener;
 
        // data is passed into the constructor
        MyRecyclerViewAdapter(Context context, List<String> data) { //// todo check
            this.mInflater = LayoutInflater.from(context);
            this.mData = data;
        }
 
        // inflates the row layout from xml when needed /////todo check
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = mInflater.inflate(R.layout.recycler_row, parent, false);
            return new ViewHolder(view);
        }
 
        // binds the data to the TextView in each row
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            String animal = mData.get(position);
            holder.myTextView.setText(animal);
        }
 
        // total number of rows
        @Override
        public int getItemCount() {
            return mData.size();
        }
 
 
        // stores and recycles views as they are scrolled off screen
        public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
            TextView myTextView;
 
            ViewHolder(View itemView) {
                //todo check
                super(itemView);
                myTextView = itemView.findViewById(R.id.tvAnimalName);
                //todo very important ---- if don't comment it's not ripple effect..........!!!!!!!!!!!!!!!!!!!
                 itemView.setOnClickListener(this);
            }
 
            @Override
            public void onClick(View view) {
                if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition());
            }
        }
 
        // convenience method for getting data at click position
        String getItem(int id) {
            return mData.get(id);
        }
 
        // allows clicks events to be caught
        void setClickListener(ItemClickListener itemClickListener) {
            this.mClickListener = itemClickListener;
        }
 
        // parent activity will implement this method to respond to click events
        public interface ItemClickListener {
            void onItemClick(View view, int position);
        }
    }
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

어댑터의 멤버필드로는 recycler view의 item을 표시하기 위해 필요한 mData가 있으며,

또한 recycler view의 해당하는 position을 클릭하였을때 응답하기위한 리스너 interface 를 들고있습니다.

 

Adapter내부로 ViewHolder 클래스와 ItemClickListener를 가지고 있습니다.

ViewHolder클래스는 recycler view의 item들을 구성하는 resource를 갖고오기 위해 사용되는 클래스이고

 

 

실제적으로 사용자눈에 보여지기위해 그려지는 부분은 Adpater 클래스의 onBindViewHolder 에서 완성되게 됩니다.

 

 

4.MainActivity.java

아래는 MainActiviity onCreate에서 recycler view를 그려주기위한 생성과정입니다.

Recylcer view를 구성하기위한 item 객체를 준비해야하며,

recycler view의 resource를 찾아와서 layout 설정을 해주고,

adpater를 생성하여 item 객체를 주입시키고, click listener 역시 연결해줍니다.

마지막으로 reycler view에 adpater를 연결 시켜주게되면 recycler view가 출력됨을 확인하실 수 있습니다.

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // data to populate the RecyclerView with
        ArrayList<String> animalNames = new ArrayList<>();
        animalNames.add("Horse");
        animalNames.add("Cow");
        animalNames.add("Camel");
        animalNames.add("Sheep");
        animalNames.add("Goat");

        // set up the RecyclerView
        RecyclerView recyclerView = findViewById(R.id.rvAnimals);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        //todo common context가 아닌 this?
        adapter = new MyRecyclerViewAdapter(this, animalNames);
        adapter.setClickListener(new MyRecyclerViewAdapter.ItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(getApplicationContext(), "You clicked " + adapter.getItem(position) + " on row number " + position, Toast.LENGTH_SHORT).show();
            }
        });
        recyclerView.setAdapter(adapter);

    }

 

 

<안드로이드 액션바 + 탭 구현>


 안드로이드에서 제공되는 Action Bar에서 Navigation Tab Mode로 변경하여 제작하는것이 금일 포스팅의 목표입니다. 안드로이드가 요즘 목표가 액션바에 네비게이션 탭을 둬서 유저에게 제공하는 UI를 제공하는거죠. 또한, 프래그먼트 사용을 적극 권장하고 있는 추세인것 같습니다. 다중의 액티비티를 만들어내는 것보다 프래그먼트를 도입하여 하나의 액티비티에서 여러기능들을 수행할 수 있도록 유도하고 있는것 같습니다.


 실제로 이러한 움직임은 불과 몇 년전의 web 에서와 비슷하다고 할 수 있는데요. web page 시장도 예전에는 frame이라는 태그가 있었고, 여러 html 파일들로 나뉘어 져있었어요. 그런데 w3c에서 frame 태그를 없애더니 하나의 페이지에서 여러 정보를 전개할 수 있는 ajax등을 권장하고 있는 추세이지요. 


아무쪼록 액션바에 네비게이션 탭모드로 설정하고 프래그먼트와 연동하는 안드로이드 실습 포스팅 진행하도록 하겠습니다.

금일 실습해볼 과제의 최종결과물 먼저 보도록 하겠습니다.

 

 

 

두번째 그림은, 첫번쨰 프래그먼트 상태인데 중복으로 눌렀을 경우, 다시한번 선택되었다는 토스트가 뜨도록 구현한 사항입니다. 구현사항이 궁금하시면, MainActivity.java 의 onTabReselected 오버라이드 메서드를 참고하시면 되겠습니다. 



먼저 탭이동에 따른 프래그먼트 먼저 생성하겠습니다.

Fragment1.xml

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


<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="First Fragment"
android:textColor="#ff000000"
android:gravity="center" />
</LinearLayout>


첫번째 프래그먼트가 생성되었을때, inflate를 전개하는 코드입니다. inflater를 통해서, 전개하는 과정의 코드가 있습니다.

FragmentOne.java

package com.android.jinss.dando;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
* Created by PARK on 2017-07-08.
*/
public class FragmentTwo extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1, container, false);
}
}


2번째 프래그먼트 xml파일입니다.

Fragment2.xml

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

<TextView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Second Fragment"
android:textColor="#ff000000"
android:gravity="center" />


</LinearLayout>


두번째 프래그먼트가 생성되었을때, inflate를 전개하는 코드입니다.

FragmentTwo.java

package com.android.jinss.dando;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
* Created by PARK on 2017-07-08.
*/
public class FragmentTwo extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment2, container, false);
}
}


그리고 fragment가 펼쳐질 activty_main.xml을 살펴보시겠습니다.두 fragment가 들어갈 부분을 LinearLayout으로 지정해놓고, id를 fragment로 추가하였습니다.

fragemetOne, Two 프래그먼트가 해당 Id가 fragment인 LinearLayout 부분에 들어가서 전개되는것을 확인하실 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.android.jinss.dando.MainActivity"
>

<LinearLayout
android:id="@+id/fragment"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</LinearLayout> 


마지막으로 Actionbar.TabListner를 implements하는 MainActivty를 살펴보시겠습니다. Fragment전개 역시 Main에서 합니다.


MainActivtiy.java

 package com.android.jinss.dando;


import android.app.ActionBar;
import android.app.Activity;

import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity implements ActionBar.TabListener {
Fragment frag1, frag2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ActionBar ab = getActionBar();
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

//타이틀바는빼고 탭만 보여주기위해 작성.
ab.setDisplayShowTitleEnabled(false);
ab.setDisplayShowHomeEnabled(false);


frag1 = new FragmentOne();
frag2 = new FragmentTwo();
ab.addTab(ab.newTab().setText("Text Fragment")
.setTabListener(this));
ab.addTab(ab.newTab().setText("Image Fragment")
.setTabListener(this));
}

@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.replace(R.id.fragment, tab.getPosition() == 0 ? frag1 : frag2);
}

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
ft.remove(tab.getPosition() == 0 ? frag1 : frag2);
}

@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
Toast.makeText(this, "Reselected!", Toast.LENGTH_LONG).show();
}
}



액션바에 네비게이션 탭을 add하여 전개하는 코드들을 살펴보았습니다. 근데 하면서, 탭의 height을 늘리고싶었는데 도무지 방법을 모르겠더라구요.

그럴때는 개발자가 직접 custom theme style.xml을 직접 작성하여서 해야하는 것같던데 자세한 방법을 찾아보다가 지쳐서 일단 pending 상태입니다.


혹시 아시는 아이디어가 있으시다면 댓글로 달아주시면 고맙겠습니다. 이상으로 안드로이드 액션바에 네비게이션 tab mode와 fragment 동시 구현에 대한 포스팅을 마치도록 하겠습니다. 감사합니다.

+ Recent posts