반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 받은 트랙백

글 보관함


이번에 POC 작업을 하면서 좀 자세히 알아야 되겠다라고 생각한게 ViewPager 기능입니다.
슬라이드 형식으로 좌우로 페이지를 넘기는 건데요.
ViewFlipper가 미리 여러개의 페이지를 ViewFlipper 에 넣은 다음에 페이지 넘김 효과를 주는 것처럼 이 ViewPager도 여러개의 페이지를 ViewPager에 넣어서 페이지 넘김 효과를 줍니다.


다른점은 ViewFlipper는 안드로이드에서 기본적으로 제공하는 API 이고 ViewPager는 기본 API 에 속하지 않은 부속 기능이라서 해당 jar 파일을 이클립스에 plug-in 한 다음에 사용한다는 겁니다.


Java Build Path - Libraries - android-support-v4.jar 등록

그리고 예제를 봤더니 ViewFlipper는 animation xml을 통해서 여러 페이지 넘김 효과를 주는 데 반해 ViewPager는 슬라이드 효과만 주고 있네요.
ViewFlipper가 더 많은 기능을 가지고 있는거 같긴한데.. 


그래도 일단 ViewPager에 대해서 한번 분석해 들어가 보겠습니다.


예제소스는 http://android-town.org/ 로 가면 무료로 제공하고 있으니 필요하신 분들은 받으셔서 보셔도 됩니다.

동영상강좌는 https://www.youtube.com/user/easyspub 에서 제공되고 있습니다.

책을 직접 구입하셔서 보시면 훨씬 더 도움이 될 것 같네요.




ViewPager

안드로이드 개발자 사이트에 소개된 ViewPager 관련 설명은 아래와 같습니다.

안드로이드 Developer 사이트 (API)
   
유저가 페이지를 오른쪽 왼쪽으로 flip 하도록 해 주는 Layout manager. 보여질 view를 generate 하기 위해 PagerAdapter를 import 해 사용해야 한다.
현재 버전은 초기 버전이고 이 후 업데이트에서는 새로운 버전에 맞게 변화가 있게 될 것이다.
viewPager는 Fragment 와 같이 어우러져서 사용된다. 이 Fragment는 각 페이지의 lifecycle을 관리하기 편리하도록 돼 있다.
ViewPager 와 함께 fragment들을 사용하기 위해 standard adapter들이 implement 돼 있다.
FragmentPagerAdaper와 FragmentStatePagerAdapter 클래스들은 full user interface를 아주 간단하게 만들 수 있도록 도와 준다.

그리고 아래 예제를 다룰 때도 설명이 될 텐데요.
ViewPager와 같이 사용되야 할 PagerAdapter 의 주요 메소드를 간단히 살펴 보겠습니다.
   
PagerAdapter API

getCount() : 현재 PagerAdapter 에서 관리할 갯수
instantiateItem() : ViewPager에서 사용할 뷰객체 생성 및 등록
destroyItem() : View 객체를 삭제 한다.
isViewFromObject() :instantiateItem 메소드에서 생성한 객체를 이용할 것인지 여부를 반환한다.
restoreState() : saveState() 상태에서 저장했던 Adapter와 page를 복구한다.
saveState() : 현재 UI 상태를 저장하기 위해 Adapter와 Page 관련 인스턴스 상태를 저장한다.
startUpdate() : 페이지 변경이 시작될 때 호출 된다.
finishUpdate() : 페이지 변경이 완료 됐을 때 호출 된다.


이제 Do it 안드로이드 앱 프로그래밍 책에서 제공하는 SampleViewPager라는 소스를 분석해 보도록 하겠습니다.
먼저 layout xml 파일 부터 분석하겠습니다.

activity_main.xml


<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" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ViewPager 사용하기"
        />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffffffff"
        />
</LinearLayout>



앱을 시작하면 화면을 구성할 첫번째 Layout xml 파일 입니다.
LinearLayout 으로 전체 구성을 잡았고 그 안에 TextView와 ViewPager 를 넣었습니다.
TextView는 좌우로는 화면을 꽉 채우고 높이는 내용만큼만 자리를 차지하도록 했네요.
그 이하 공간은 모두 ViewPager 가 차지할 겁니다.
아마 ViewPager 사용하기라는 글자는 그대로 있으면서 그 아래 ViewPager 공간만 slide 될 것 같습니다.

다음은 자바 파일을 보죠.



public class MainActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // get a ViewPager reference and set adapter for it
        ViewPager pager = (ViewPager) findViewById(R.id.pager);
        ViewPagerAdapter adapter = new ViewPagerAdapter(this);
        pager.setAdapter(adapter);
    }

    public class ViewPagerAdapter extends PagerAdapter {
        // sample names
        private String[] names = { "John", "Mike", "Sean" };
        // sample image resource ids
        private int[] resIds = {R.drawable.dream01, R.drawable.dream02, R.drawable.dream03};
        // sample call numbers
        private String[] callNumbers = {"010-7777-1234", "010-7788-1234", "010-7799-1234"};

        /**
         * Context
         */
        private Context mContext;

        /**
         * Initialize
         *
         * @param context
         */
        public ViewPagerAdapter( Context context ) {
            mContext = context;
        }
        /**
         * Count of pages
         */
        public int getCount() {
            return names.length;
        }
        /**
         * Called before a page is created
         */
        public Object instantiateItem(View pager, int position) {
            // create a instance of the page and set data
            PersonPage page = new PersonPage(mContext);
            page.setNameText(names[position]);
            page.setImage(resIds[position]);
            page.setCallNumber(callNumbers[position]);

            // add to the ViewPager
            ViewPager curPager = (ViewPager) pager;
            curPager.addView(page, position);

            return page;
        }

        /**
         * Called to remove the page
         */
        public void destroyItem(View pager, int position, Object view) {
            ((ViewPager)pager).removeView((PersonPage)view);
        }
        public boolean isViewFromObject(View view, Object object) {
            return view.equals(object);
        }
        public void finishUpdate( View view ) {
        }
        public void restoreState( Parcelable p, ClassLoader c ) {
        }
        public Parcelable saveState() {
            return null;
        }
        public void startUpdate( View view ) {
        }
    }
}


위 이미지는 여기에서 다운 받은 이미지 입니다.


제 Laptop 에 영문 윈도우가 깔려 있어서 한글이 깨지네요.
그래서 한글로 된 주석은 지웠습니다. 이전 글에 있는 링크에서 소스파일을 다운 받아 보시면 한글 주석을 보실 수 있을 겁니다.

우선 맨 먼저 실행될 onCreate() 메소드를 보겠습니다.
activity_main.xml 을 layout을 구성하는 xml로 사용하겠다고 세팅한 라인이 있구요.
바로 그 밑에 ViewPager의 instance를 만들고 거기에 activity_main.xml 에 선언한 id가 pager 인 ViewPager를 대입했습니다.
그리고 ViewPagerAdapter 에 대한 인스턴스도 만들어서 방금전 만들었던 ViewPager 에 이 adapter를 세팅했습니다.

이 ViewPagerAdapter 는 어디서 왔을 까요? 안드로이드에서 제공하는 함수는 아니고 개발자가 만든 클래스 입니다.
바로 아래에 그 클래스가 있습니다.

ViewPagerAdater 클래스는 PagerAdapter를 extends 하네요.
이 PagerAdapter 는 안드로이드에서 제공하는 API 입니다.

이 PagerAdapter에 대해 안드로이드 개발자 페이지 (API) 에서는 아래와 같이 설명했네요.


ViewPager안에 페이지들을 생성하기 위해 Base Class 는 이 adapter를 제공한다. 이것과 관련해 좀 더 특정 implementation을 사용하고 싶어 할 것이다.
예를 들어 FragmentPagerAdapter 나 FragmentStatePagerAdapter 같은....

PagerAdapter를 implement 하면 최소한 다음과 같은 메소드들을 override 해야 한다.



그 다음에도 계속 설명이 나오는데요.
너무 길고.. 나중에 필요하면 따로 정리를 해야겠습니다.

일단 이 PagerAdapter에는 ViewPager에서 사용할 페이지들이 들어가는군요.
이 예제에서는 이 PagerAdapter를 extends 한 ViewPagerAdapter인 클래스를 만들었구요. 여기다가 보일 페이지들을 넣을건가 봅니다.

그러면 이 ViewPagerAdapter 클래스를 살펴 보겠습니다.

처음에는 names, resIds, callNumbers라고 하는 배열들을 만들었습니다.
names에는 사람 이름이 그리고 resIds에는 이미지 resource들이 마지막으로 callNumbers 에는 전화번호들이 들어가는군요.

그 다음에는 Context를 초기화 했습니다.

다음 생성자에서는 Context를 파라미터로 받아서 mContext에 대입했네요.
이 클래스가 호출이 되면 가장 먼저 이 생성자가 실행이 될 겁니다.
그러니까 가장 먼저 일단 context를 세팅한다는 얘기죠.

이 Context는 어플리케이션이 실행되기 위해 기본적으로 필요한 정보나 환경들에 대한 내용인데 안드로이드에서 제공하는 겁니다.
getSystemService() 메소드를 통해서 여러 많은 정보들을 가져다 쓸 수도 있죠.
그리고 Activity가 이 Context를 상속 받았습니다.
그러니까 Activity 를 상속받은 클래스에서는 이 Context를 그냥 this 라는 구문으로 사용할 수 있는 겁니다.
하지만 Activity 가 아닌 다른 Context를 상속받지 않은 클래스에서는 this 라는 것으로 Context를 사용하도록 할 수 없겠죠.
제 기억엔 별도로 만든 클래스에서  Intent 나 뭐 이런걸로 Page 전환 할 때 문제가 발생할 수 있습니다.
모르면 당황할 텐데요. 그냥 View의 객체가 있으면 그 View의 getContext() 를 사용해서 처리하시면 됩니다.
혹은 getApplicationContext() 를 사용하셔도 되구요.
저는 대부분 getApplicationContext()를 사용했던 것 같습니다.

그 다음에는 위에서 설명했던 PagerAdapter API 에 있던 메소드들이 있네요.
getCount() 메소드에는 페이지 수가 들어간다는데 이 예제에서는 names 배열내의 아이템 갯수가 페이지 수가 되도록 했습니다.

그 다음 메소드가 ViewPager에서 사용할 View 객체를 생성하고 등록한다는 instantiateItem() 메소드 입니다.

이 메소드는 View 와 int 형의 position을 파라미터로 받습니다.
그 내용은 우선 PersonPage라는 클래스의 인스턴스를 만드네요. 여기에 mContext를 파라미터로 던지구요.
이 PersonPage는 안드로이드에서 제공하는 API 클래스가 아니니까 아마 개발자가 따로 만들었을 겁니다.
나중에 그 클래스도 분석해 보게 되겠네요.
일단 뭔지는 모르지만 PersonPage의 인스턴스를 만들었고 여기에 names와 resIds와 callNumbers 에 있는 아이템들을 세팅합니다.
보니까 PersonPage 클래스에는 setNameText(), setImage(), setCallNumber() 라는 메소드들이 작성 돼 있나 봅니다.

그 다음에는 ViewPager의 인스턴스를 만들고 이 ViewPager에 이 PersonPage (page)와 position (페이지 번호)를 add 해 줍니다.
그러면 ViewPager에는 해당 페이지의 name과 image와 callNumber들이 들어있을 테고 즉 화면에 그게 display 되겠네요.




다음은 view 객체를 삭제하는 destroyItem() 메소드가 있습니다.
View와 int 형으로 페이지번호와 Object 를 파라미터로 봤습니다.
그리고 그 내용은 해당 view를 remove 합니다.
그 다음 finishUpdate(), restoreState(),startUpdate() 는 모두 내용이 없는 껍데기 메소드들이구요.
즉 다른 특별한 코딩을 하지 않아서 어떤 기능 추가나 변경 없이 그냥 override 만 했을 뿐이네요.
saveState() 메소드에서도 그냥 null을 반환할 뿐입니다.

이렇게 되면 MainActivity.java는 모두 분석했습니다.

ViewPager 를 사용하도록 레이아웃 xml 과 onCreate() 에서 선언을 해주고 ViewPager의 페이지들은 ViewPagerAdapter 클래스에서 만들었습니다.
이 ViewPagerAdapter에는 각 화면들에 들어갈 내용들을 배열에 넣었구요.
그 화면들을 어떻게 구성할 것인지는 PersonPage라는 클래스를 외부에 따로 만들어서 사용하고 있습니다.

반응형

Comment


    지난 글에서 display 하는 것은 모두 완료 됐습니다.
   
    이제 볼 것은 뭘까요?
    아까 ViewFlipper에 touch 리스너를 달았습니다.
   


    그러면 ViewFlipper에 어떤 touch 이벤트가 발생했을 때 다른 동작을 할 수 있도록 코딩을 할 수 있는데요.
    바로 그 코딩을 하는 onTouch() 메소드를 분석해 봐야 할 차례입니다.
   


        public boolean onTouch(View v, MotionEvent event) {
        if(v != flipper) return false;

        if(event.getAction() == MotionEvent.ACTION_DOWN) {
            downX = event.getX();
        }
        else if(event.getAction() == MotionEvent.ACTION_UP){
            upX = event.getX();

            if( upX < downX ) {  // in case of right direction
 
                flipper.setInAnimation(AnimationUtils.loadAnimation(getContext(),
                        R.anim.wallpaper_open_enter));
                flipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(),
                        R.anim.wallpaper_open_exit));

                if (currentIndex < (countIndexes-1)) {
                    flipper.showNext();

                    // update index buttons
                    currentIndex++;
                    updateIndexes();
                }
            } else if (upX > downX){ // in case of left direction

                flipper.setInAnimation(AnimationUtils.loadAnimation(getContext(),
                        R.anim.push_right_in));
                flipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(),
                        R.anim.push_right_out));

                if (currentIndex > 0) {
                    flipper.showPrevious();

                    // update index buttons
                    currentIndex--;
                    updateIndexes();
                }
            }
        }
        return true;
    }
   


    이 메소드는 View 와 MotionEvent를 파라미터로 받습니다.
    첫번째 줄에는 전달 받은 View 가 flipper 가 아니면 false를 return 하라고 돼 있네요.
    여기서 다루는 view 는 flipper 이니까 이 라인은 건너 뛰고 다음 라인이 실행 될 겁니다.
   
    다음 if 문을 보면 해당 Action 이 Down 일 경우 downX에 touch 점의 x 좌표를 대입합니다.
    다음 else if 문은 action 이 up 인 경우 입니다.
   
    우선 upX에 현재의 x 좌표를 대입시키구요.
    upX 가 downX 보다 작으면 그러니까 사용자가 오른쪽으로 손가락을 slide 할 경우가 되겠네요.
    그런 경우 flipper에 세팅하는 애니메이션을 wallPaper_open_enter 를 선택합니다.
    이 애니메이션은 res 폴더 밑의 anim 폴더 밑에 xml 형태로 저장돼 있습니다. 그 내용은 아래와 같습니다.
   


    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@anim/decelerate_interpolator"
        android:detachWallpaper="true">
    <scale android:fromXScale="2.0" android:toXScale="1.0"
           android:fromYScale="2.0" android:toYScale="1.0"
           android:pivotX="50%p" android:pivotY="50%p"
           android:duration="@android:integer/config_mediumAnimTime" />
    </set>



    일단 Scale이 2.0에서 1.0 바뀌네요. X,Y 모두 다요.
    그 다음엔 pivotX와 pivotY 가 각각 50%p 의 값을 가집니다.
    그리고 duration은 android의 config.xml에 정의 된 config_mediumAnimTime 를 가져옵니다.
    그 값은 400 이구요. 주석을 보면 medium-length animation을 위한 값(milliseconds)라고 돼 있네요.
    pivot은 회전축이라는 뜻으로 애니메이션 효과에 사용됩니다. 여기서는 flip 효과에 사용되나 보네요.
   
    다시 자바로 돌아가면 그 다음에는 wallpaper_open_exit 애니메이션이 실행되네요.
   


    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@anim/decelerate_interpolator"
        android:zAdjustment="top">
    <scale android:fromXScale="1.0" android:toXScale=".5"
           android:fromYScale="1.0" android:toYScale=".5"
           android:pivotX="50%p" android:pivotY="50%p"
           android:duration="@android:integer/config_mediumAnimTime" />
    <alpha android:fromAlpha="1.0" android:toAlpha="0"
            android:duration="@android:integer/config_mediumAnimTime"/>
    </set>



    여기엔 alpha 값도 설정이 됐군요. 선명한 상태에서 완전 투명해 져서 안보이는 상태로 갑니다.
   
    그리고 나서 currnetIndex 가 currentIndexes-1 보다 적을 경우 는 flipper에 있는 next 를 보여 줍니다.
    그리고 currntIndex 에 1을 더해주고 updateIndexes()를 호출 합니다.
   
    그럼 updateIndexes() 를 보죠.
   


    private void updateIndexes() {
        for(int i = 0; i < countIndexes; i++) {
            if (i == currentIndex) {
                indexButtons[i].setImageResource(R.drawable.green);
            } else {
                indexButtons[i].setImageResource(R.drawable.white);
            }
        }
    }



    countIdexes (3) 만큼 for 문을 돌리구요. i 가 currentIndex 일 경우 indexButtons 의 리소스 이미지를 바꿔 줍니다.
    즉 현재 내용이 몇번째인지 그것을 알아내서 해당 버튼 이미지도 녹색으로 바꾸고 나머지는 흰색으로 보여지게 하는 부분 입니다.
   
    다시 onTouch() 메소드의 다음 부분을 이어서 보면요.
   
    else if (upX > downX) 일 경우 이 경우는 왼쪽방향일 경우라고 주석에 쓰여져 있네요.
    이럴 경우 애니메이션으로 push_right_in.xml와 push_right_out 을 실행 하라고 돼 있습니다.
   
    push_right_in.xml
   

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="-100%p" android:toXDelta="0%p" android:duration="300"/>
    <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" />
</set>

    push_right_out
    <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0%p" android:toXDelta="100%p" android:duration="300"/>
    <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="300" />
</set>



    그리고 currentIndex가 0 보다 크면 flipper의 이전 내용을 보여 줍니다.
    currentIndex에는 -1을 해 주고요.
    그 다음에 updateIndexes() 메소드를 실행해서 이미지 버튼의 색을 바꿔주고요.
   
   
    여기까지가 viewFlipper 예제에 대한 분석이었습니다.

반응형

Comment


이제 실제 메소드들을 보면서 분석해 보겠습니다.
일단 제일 먼저 실행 될 것 같은 init() 메소드를 보죠. 주석에도 Initialize (초기화)라고 돼 있으니까 왠지 이게 제일 먼저 실행 될 것 같네요.
(오버로딩 된 ScreenViewFlipper 메소드를 봐도 이 init() 메소드를 불러오는 것 만 있습니다.)


이 그림은 viewFlipper를 잘 보여주는 것 같아 구글에서 갖다 썼습니다.

이 화면에 대한 소스나 설명은 여기로 가시면 보실 수 있습니다.



    public void init(Context context) {
        setBackgroundColor(0xffbfbfbf);

        // Layout inflation
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.screenview, this, true);

        buttonLayout = (LinearLayout) findViewById(R.id.buttonLayout);
        flipper = (ViewFlipper) findViewById(R.id.flipper);
        flipper.setOnTouchListener(this);

        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
        params.leftMargin = 50;

        indexButtons = new ImageView[countIndexes];
        views = new TextView[countIndexes];
        for(int i = 0; i < countIndexes; i++) {
            indexButtons[i] = new ImageView(context);

            if (i == currentIndex) {
                indexButtons[i].setImageResource(R.drawable.green);
            } else {
                indexButtons[i].setImageResource(R.drawable.white);
            }

            indexButtons[i].setPadding(10, 10, 10, 10);
            buttonLayout.addView(indexButtons[i], params);

            TextView curView = new TextView(context);
            curView.setText("View #" + i);
            curView.setTextColor(Color.RED);
            curView.setTextSize(32);
            views[i] = curView;

            flipper.addView(views[i]);
        }
    }



    우선 화면의 배경색을 세팅했네요. 0xffbfbfbf 이게 무슨 색인지는 잘 모르겠습니다. 나중에 실행 시켜보면 알겠죠.
    그 다음 LayoutInflater 의 instance를 만들고 여기에 context의 SystemService 에서 layout inflater를 대입했습니다.
   
    이 LayoutInflater에 대해 google developer 사이트에서는 아래와 같이 설명해 놓았습니다.
    layout XML 파일을 해당하는 View object로 초기화 하는 것. getLayoutInflater()나 getSystemService(String)을 가지고 standard LayoutInflater 인스턴스를 가져옴.
    이 standard LayoutInflater 인스턴스는 현재의 context에 이미 있고 디바이스에 맞게 configure 된 것들 입니다.
   
    즉 XML에서 선언된 어떤 view를 가져와서 그 위에 object들을 배열할 때 사용되는가 봅니다.
   
    그 아래 줄을 보시면 이 inflater에 아까 봤던 screenview.xml을 넣었습니다.
    이 xml 에는 <LinearLayout> (id=buttonLayout)과 <ViewFlipper> (id=flipper)가 들어 있었죠.
    이제 이 inflater가 이것들을 구체적으로 어떻게 사용하는지 볼까요?
   
    아까 예상했던 대로 buttonLayout 에는 screenview.xml에 있는 id가 buttonLayout 이라는 LinearLayout 을 세팅합니다.
    다음줄엔 flipper 에 id가 flipper 인 ViewFlipper를 세팅하구요. 이 flipper에는 touchListener를 세팅합니다.
   
    예상컨데 이 buttonLayout에는 button들 (3개로 선언했었죠?)이 배치되겠고 flipper에는 표시될 screen들이 들어가겠네요.
    이 screen을 손가락으로 touch(slide) 하면 화면이 바뀌겠구요.
   
    이것을 구체적으로 어떻게 코딩하는지 계속 알아 보겠습니다.



   
    LinearLayout.LayoutParams 인스턴스를 만들고 여기에 이 view에 대한 width와 height 가 Wrap_content 인 LayoutParams를 대입시킵니다.
    그리고 이 params 에 leftMargin을 50으로 세팅하구요.
   
    이 값은 나중에 아까 선언했던 buttonLayout 에 적용이 되겠죠.
   
    다음에는 indexButtons에 new ImageView[countIndexes]를 대입합니다. countIndexes는 제일 처음에 3으로 선언했었죠?
   
    그리고나서 이 countIndexes 만큼 for문을 돌립니다.
    그 for문을 돌고 나면 3개의 ImageView 가 만들어 질 거고 이 이미지 뷰들은 indexButtons 배열에 담길겁니다.
    만약 i가 0이면 즉 제일 첫번째 것이면 그 ImageView에는 green.png 가 대입 되겠고 그렇지 않으면 white.png가 대입이 될 겁니다.
    그러니까 첫번째는 초록색 버튼이 되고 나머지 두 버튼은 흰색이 되겠네요.
    이 ImageView에 padding 값을 주고 buttonLayout에 지금까지 세팅한 ImageView(indexButtons)를 넣습니다.
    buttonLayout.addView(indexButtons[i], params);
    보시면 params 도 pass 하죠? 이 params는 아까 leftMargin을 50으로 세팅한 겁니다.
   
    그 다음엔 TextView 를 하나 만드네요.
    이 텍스트 뷰에는 View # = i 라는 텍스트가 있고 색은 빨간색이고 size는 32 라고 세팅이 돼 있습니다.
    이 값을 views 배열에 넣는데요.
   
    저 for 문 바로 위에 보니까 vews 라는 TextView 배열이 선언돼 있네요. 배열 크기도 countIndexes 즉 3개로 선언돼 있구요.
    이 부분을 보지 못하고 그냥 넘어갔네요.
   
    이 text를 가진 views 배열을 flipper에 넣는 것이 이 for 문의 마지막이자 이 init메소드의 마지막 입니다.
   
    자 여기까지가 바로 screenview.xml에서 그냥 내용 없이 선언된 LinearLayout 과 ViewFlipper에 내용을 채워 넣은 겁니다.
    LinearLayout 에는 세개의 버튼이 놓여지게 되고 버튼 색은 녹색, 흰색, 흰색이 세팅됐습니다.
    그리고 ViewFlipper 에도 3개의 내용이 들어가 있는데 View #1, View #2, View #3 이라는 텍스트가 있습니다.
    그 텍스트들은 빨간색이고 크기가 32입니다.
   
    이제 display 하는 것은 모두 완료 됐습니다.
   
    이제 볼 것은 뭘까요?
    아까 ViewFlipper에 touch 리스너를 달았습니다.
   
    그러면 ViewFlipper에 어떤 touch 이벤트가 발생했을 때 다른 동작을 할 수 있도록 코딩을 할 수 있는데요.
    바로 그 코딩을 하는 onTouch() 메소드를 분석해 봐야 할 차례입니다.

반응형

Comment


지난 월요일 화요일 이틀동안 POC (Proof of Concept)용으로 안드로이드 Native App 을 하나 개발 했습니다.

Native App 은 거의 2년만에 개발하는 것 같은데..

이번에 새로운 개념들도 여러개 배웠습니다.

그 내용들을 정리해 볼까 하는데요.


오늘은 그 중에 ViewFlipper 에 대해 정리해 보겠습니다.


공부하는 방법은 제가 Do It 안드로이드 앱 프로그래밍이라는 책에서 제공하는 동영상을 보고 개념도 다시 잡고 거기서 제공하는 소스를 다운받아서 보기도 하고 했는데요.


그 책에서 제공하는 viewFlipper 관련 예제 소스를 자세히 분석하면서 공부를 하겠습니다.


예제소스는 http://android-town.org/ 로 가면 무료로 제공하고 있으니 필요하신 분들은 받으셔서 보셔도 됩니다.

동영상강좌는 https://www.youtube.com/user/easyspub 에서 제공되고 있습니다.

책을 직접 구입하셔서 보시면 훨씬 더 도움이 될 것 같네요.


그럼 그 책에서 제공하고 있는 SampleViewFlipper 를 보겠습니다.


우선 첫번째 실행되는 layout xml을 보겠습니다.


activity_main.xml


<RelativeLayout 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" >

    <org.androidtown.ui.flipper.ScreenViewFlipper
        android:id="@+id/screen"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp" />
</RelativeLayout>


Launcher Activity 의 Layout xml 은 위와 같이 돼 있습니다.
이 RelitiveLayout 의 대상 공간은 화면 전체이구요.
RelativeLayout 으로 세팅하고 그 내부에는 ScreenViewFlipper 클래스를 import 하는 태그만 있습니다.
이 flipper의 id 는 screen 이고 margin 10dp 로 한채로 대상 공간이 그 parent 인 RelativeLayout 을 꽉 채웁니다.


이클립스에 있는 Graphical Layout 을 통해 보면 아래와 같은 화면이 나옵니다.




이제 JAVA 소스코드를 봐야겠네요.


public class MainActivity extends Activity {

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

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}


바로 이 MainActivity 가 첫번째 화면인데요. onCreate() 메소드내에 위에서 본 activity_main.xml 을 Content View로 세팅을 했습니다.
그게 다 입니다.

밑에 있는 onCreateOptionMenu() 메소드는 override 한 것인데 안드로이드 디바이스 밑의 menu 버튼을 누르면 나오는 메뉴들을 세팅하는 메소드 입니다.
실제 이 앱과는 관련이 없으니까 무시하셔도 됩니다.

결국은 앱을 실행하는 첫번째 Activity에는 아무론 것도 화면에 표시를 하지 않네요.
이제 activity_main.xml에서 import한 ScreenViewFlipper.java 를 봐야겠습니다.




그럼 우선 이 ScreenViewFlipper에서 사용하는 layout 파일인 screenview.xml을 보겠습니다.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <LinearLayout
        android:id="@+id/buttonLayout" 
        android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        >
    </LinearLayout>   
    <ViewFlipper
        android:id="@+id/flipper" 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >   
    </ViewFlipper>
</LinearLayout>


일단 LinearLayout 을 사용했습니다. width와 height가 match_parant니까 위의 xml 파일에서 세팅된 <org.androidtown.ui.flipper.ScreenViewFlipper ...... />의 크기 만큼 그 대상 공간이 됩니다.
여기서는 계속 화면 전체가 되겠죠.

그 다음 LinearLayout 내부에 또 LinearLayout이 있습니다. 그 크기는 wrap_content 로 돼 있는데 그것은 이 LinearLayout에 들어가는 내용의 크기 만큼 설정을 하겠다고 세팅돼 있습니다.
그런데 그 안에 아무것도 없네요. 즉 화면에 표시할 게 아무것도 없습니다. 나중에 혹시 java 파일에서 이 LinearLayout을 불러내서 다른 object를 setting 하면 그 때 화면에 뭔가가 표시 될 수는 있을 겁니다.
(id가 buttonLayout 이니까 이 id로 불러낼겁니다.)

그 아래는 <ViewFlipper> 태그가 있습니다. 여기에 오늘 공부하는 ViewFlipper를 표시할 겁니다.

여기까지 보니까 위에는 버튼이 달릴거고 (내부 LinearLayout id 가 buttonLayout인것을 가지고 아직까지는 그냥 추정해 보는 겁니다.) 그 아래에 ViewFlipper 가 달려서 페이지 전환이 이뤄질 것 같습니다.


아직까지는 이 Layout에 어떤 VIew나 Widget이 표시되지는 않습니다.




ViewFlipper에 대한 Android Developer 싸이트의 설명은 이렇습니다.
두개 이상의 View들이 add돼 있고 이 view들이 animate 될 간단한 ViewAnimator. 한번에 한 child만 보인다. 설정을 한다면 일정한 interval을 두고 각각의 child 를 자동으로 flip 되도록 할 수 있다.

flip이라는 의미가 뒤집다라는 거니까 페이지들을 child로 설정했다면 이 페이지가 뒤집히는 효과를 주면서 화면이 전환되게 할 수 있겠네요.
또 view에는 여러개가 해당 될 수 있으니까 다른 object 별로도 이런 flip 효과를 줄 수 있겠습니다.

그럼 이제 본격적인 내용이 있는 ScreenViewFlipper를 한번 분석해 보죠.



public class ScreenViewFlipper extends LinearLayout implements OnTouchListener {

    /**
     * Count of index buttons. Default is 3
     */
    public static int countIndexes = 3;

    /**
     * Button Layout
     */
    LinearLayout buttonLayout;

    /**
     * Index button images
     */
    ImageView[] indexButtons;

    /**
     * Views for the Flipper
     */
    View[] views;

    /**
     * Flipper instance
     */
    ViewFlipper flipper;

    /**
     * X coordinate for touch down
     */
    float downX;

    /**
     * X coordinate for touch up
     */
    float upX;

    /**
     * Current index
     */
    int currentIndex = 0;


    public ScreenViewFlipper(Context context) {
        super(context);
       
        init(context);
    }

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

    /**
     * Initialize
     *
     * @param context
     */
    public void init(Context context) {
        setBackgroundColor(0xffbfbfbf);

        // Layout inflation
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.screenview, this, true);

        buttonLayout = (LinearLayout) findViewById(R.id.buttonLayout);
        flipper = (ViewFlipper) findViewById(R.id.flipper);
        flipper.setOnTouchListener(this);

        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
        params.leftMargin = 50;

        indexButtons = new ImageView[countIndexes];
        views = new TextView[countIndexes];
        for(int i = 0; i < countIndexes; i++) {
            indexButtons[i] = new ImageView(context);

            if (i == currentIndex) {
                indexButtons[i].setImageResource(R.drawable.green);
            } else {
                indexButtons[i].setImageResource(R.drawable.white);
            }

            indexButtons[i].setPadding(10, 10, 10, 10);
            buttonLayout.addView(indexButtons[i], params);

            TextView curView = new TextView(context);
            curView.setText("View #" + i);
            curView.setTextColor(Color.RED);
            curView.setTextSize(32);
            views[i] = curView;

            flipper.addView(views[i]);
        }
    }
   
    /**
     * Update the display of index buttons
     */
    private void updateIndexes() {
        for(int i = 0; i < countIndexes; i++) {
            if (i == currentIndex) {
                indexButtons[i].setImageResource(R.drawable.green);
            } else {
                indexButtons[i].setImageResource(R.drawable.white);
            }
        }
    }

    /**
     * onTouch event handling
     */
    public boolean onTouch(View v, MotionEvent event) {
        if(v != flipper) return false;

        if(event.getAction() == MotionEvent.ACTION_DOWN) {
            downX = event.getX();
        }
        else if(event.getAction() == MotionEvent.ACTION_UP){
            upX = event.getX();

            if( upX < downX ) {  // in case of right direction
 
                flipper.setInAnimation(AnimationUtils.loadAnimation(getContext(),
                        R.anim.wallpaper_open_enter));
                flipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(),
                        R.anim.wallpaper_open_exit));

                if (currentIndex < (countIndexes-1)) {
                    flipper.showNext();

                    // update index buttons
                    currentIndex++;
                    updateIndexes();
                }
            } else if (upX > downX){ // in case of left direction

                flipper.setInAnimation(AnimationUtils.loadAnimation(getContext(),
                        R.anim.push_right_in));
                flipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(),
                        R.anim.push_right_out));

                if (currentIndex > 0) {
                    flipper.showPrevious();

                    // update index buttons
                    currentIndex--;
                    updateIndexes();
                }
            }
        }
        return true;
    }
}



우선 메소드들을 선언하기 전에 나오는 object들을 선언하는 것부터 보겠습니다.
첫번째는 countIndexes=3; 인데 주석을 보니까 index 버튼들의 숫자라고 하네요. 디폴트는 3이구요.
그 다음 LinearLayout을 buttonLayout 이라는 이름으로 하나의 객체를 만들어 놨습니다.
아직 정확하게는 모르지만 이 객체에 screenview.xml 에 있었던 buttonLayout 이라는 id를 가진 LinearLayout 을 세팅할 것 같네요.
그리고 indexButtons 라는 이름으로 ImageView 배열을 선언했구요. 여기에는 위에서 3개로 선언된 index button 이미지가 세팅될것 같습니다.
그 다음에는 views라는 이름으로 View 배열이 있습니다. 주석에는 Flipper들을 위한 View들이라고 돼 있네요.
다음줄에서 ViewFlipper의 instance를 만들었습니다. 그 이름은 flipper 이구요.
그 다음은 down시 x 좌표를 받을 downX라는 이름으로 float 객체를 만들었고 up시 x 좌표를 받을 upX도 있습니다.
그리고 현재의 index는 0이라고 선언한 줄이 나옵니다. 이름은 currentIndex 이고 정수형입니다.


메소드에 대한 분석들은 다음 글에서 이어나가겠습니다.

반응형

Comment


이번 주말은 안드로이드 동영상 강좌만 들으면서 보냈습니다.

이지스퍼블리싱 출판사에서 만든 Do it 안드로이드 앱 프로그래밍이란 책을 바탕으로 제공하는 동영상 강좌 입니다.


카페 주소는 http://cafe.naver.com/easyispub/945 이고

유투브 주소는 https://www.youtube.com/watch?v=Xb9300qU52Y&list=PL5C6E85EFC6E406EF 입니다.





금요일에 안드로이드 관련 업무를 하나 받았거든요.

기존에 개발된 앱을 보여주면서 안드로이드 태블릿 버전으로 POC (Proof of Concept)를 하나 만들어 보라고요. 수요일 오전까지요.


Android Native App Programming 을 잠시 떠나 있어서 예전에 했던것을 다시 떠올리기 위해 자료를 찾다가 이 동영상 강좌를 보게 됐습니다.


1편부터 10편까지 봤습니다. 개념정리에 아주 도움이 됐습니다.

안드로이드 앱 프로그래밍에 관심 있으신 분들께 적극 추천 합니다.


한국에 있다면 당장에 저 책 사서 활용했을 텐데 여기서는 그 책을 구할 수가 없어서... ;;


거의 토요일 하루 종일 봤는데요. 아마 10시간 가까이 봤을 것 같습니다.

오늘 (일요일)에는 법륜 스님이 이 먼 미국까지 오셔서 더군다나 제가 사는 지역까지 오셔서 즉문즉설을 해 주신다고 해서 거기 갈 계획입니다.


그 즉문즉설 다녀온 다음 화요일까지 빡세게 해서 POC를 위한 안드로이드 태블릿 앱을 하나 만들겁니다.


살짝 긴장되는데 기분 좋은 긴장이네요.


아래 글은 동영강강좌 10편을 들으면서 그냥 끄적거린 메모 입니다.

나중에 다시 기억 떠올리는데 도움이 될 것 같아서 이 블로그에 정리해 둡니다.



* 안드로이드 태블릿

허니컴 부터 시작 : 안드로이드 3.0 (Level 11,12,13)
아이스크림 샌드위치부터 phone + Tablet 시작 : 안드로이드 4.0 (Level 14,15)
현재는 젤리빈 : 안드로이드 4.1,4.2,4.3 (Level 16,17,18)
차후 KitKat 예정 : 안드로이드 4.4 (Level 19)

* 기억해야 할 것들


리스너 달기 : setOnClickListener
intent
화면 전환하기 : intent(intent.ACTION_VIEW,Uri.parse("웹사이트 주소 or tel:전화번호 etc."));

layout 에 있는 객체 불러오기 : (Button) findViewById(R.id.buttonname);

* Activity 간 전환
- layout 에 xml 하나 더 만듬
- Intent myIntent = new Intent(getApplicationContext(), NewActivity.class);
- startActivity(myIntent)
- Manifest 파일에 새로운 Activity 등록
=> 이러면 NewActivity.class 액티비티가 호출 됨

* 안드로이드 버전별 주요 기능

Proyo (2.2) : Flash, SD 카드에 설치, Backup API, Push Message 지원
GengerBread (2.3) : NFC (전자 결재), front Camera, 자이로스코프 센서, Internet 전화(SIP) 지원
HoneyComb (3.0) : Tablet UI, 화면 분할, HTTP Live Streming 지원
Icecream Sandwich (4.0) : 홀로그래픽 UI 와 버추얼 키, 얼굴인식과 음성인식 텍스트 입력, NFC 빔 공유와 WiFi 다이렉트 지원

* Structure of Android Project folder
/src
/Android <version>
/res
/gen
/assets
AndroidManifest.xml
default.properties


sdk/tools/hierarchyviewer.bat - 해당 액티비티의 구조도를 볼 수 있음

* apk 만들기
Android tools - export signed application - keystore 제공 - ....

* 화면구성 - View,Layout,Widget

* Widget - fill_parent, wrap_content etc.

* Layout 종류
LinearLayout : android:orientation = vertical, horizontal
Releative Layout
Frame Layout : 여러개의 View를 중첩시킨 후 각각의 뷰에 대해 보이거나 보이지 않게 하면 뷰 전환 가능. object.setVisibility(View.)
Table Layout
Scroll View

* setContentView(R.Layout.main);

* DDMS - LogCat 로그 보기

* startActivityForResult(intent,constant)
  - 다른 액티비티에서 데이터를 받기 위해 사용. (onActivityResult())
  setResult(code,resultIntent), resultIntent.putExtra("name","john");

* parent class 의 method override 하기
  - right mouse button - source - override/Implement method - select method

* Toast.makeText(context,text,duration);

* startActivity(), startService(), bindService(), broadcastIntent()

* ACTION_DIAL tel:1111111, ACTION_VIEW tel:1111, ACTION_EDIT content://contacts/people/2, ACTION_VIEW

content://contacts/people

* Implicit Intent : Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://m.yahoo.com");
* Explicit Intent : Intent myIntent = new Intent(getApplicationContext(),NewActivity.class);

* FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_CLEAR_TOP, FLAG_ACTIVITY_NO_HISTORY

* Parcelable : 전달하는 데이터가 선언한 객체일 경우, bundle.getParcelable();




* Shared Preference : 간단한 정보 저장했다 불러올 때 사용. Cache 같은 것 (onPause())
  SharedPreference myPref = getSharedPreferences("myPref",Activity_MODE_WORLD_WRITEABLE);
  SharedPreferences.Editor myEditor = myPref.edit();
  myEditor.putString("name","Tom");
  myEditor.commit();
* onResume()
  SharedPreferences myPref = getSharedPreference("myPref",Activity.MODE_WORLD_WRITEABLE);
  if(myPref != null && myPref.contains("name")){
    String name = myPref.getString("name");
  }

* 로그 출력 : Log.d("tag","Message");

* Service : startService(), stopService()
            android:process=":remote" - Manifesto 에 별도의 프로세스를 선언할 수 있음 -> 앱이 Destroy 되도 계속 동작함

* Content Provider
  CONTENT_URI , content://... 로 접근., http:// 로 웹사이트 접근
  Cursor를 이용해 결과 데이터 확인 (Database)
  ContentResolver : getContentResolver(), query()

  ex) ContentResolver resolver = getContentResolver();
      resolver.query(uri,projection,selection,selectionArgs,sortOrder);
      Cursor cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI,null,null.null.null);
      int count = cursor.getCount();
      Toast.makeText(this,"text",duration);
      => add permissions : READ_CONTACTS
      for(int i = 0; i<count; i++){
        cursor.moveToNext();
        String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
        Toast.makeText(this,"name " + i + name,duration);
    }

* Manifest
  <activity>,<intent-filter><action android:name = ".... .MAIN"> <category android:name = "......LAUNCHER>,

<style ....>

* AlertDialog
  AlertDialog dialog = null;
  switch(id){
      case 1:
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("");
         builder.setPositiveButton("",new DialogInterface.OnClickListener())
        
        public void onClick(DialogInterface dialog, int which) {
            Toast.makeText.....
        }
        });
        builder.create();
  }

* AlertDialog 를 Activity 로 만들기 (로그인 화면??)
  AlertDialog가 떠야 되는 장소에 코딩
  setContentView(R.layout.dialog); // dialog.xml 만들기 (바탕화면,로그인 창 - 바탕화면은 이미지를 매번 바꿀 것)
  Manifest - <activity android:name="DialogActivity" android:theme="@android.style/Theme.Dialog"></activity>
  => AlertDialog box 디자인 하는 방법
 


반응형

Comment

안드로이드 개발 환경 구축하기

2013. 9. 6. 05:32 | Posted by 솔웅


안드로이드 개발 환경 구축



오랜만에 다시 안드로이드 애플리케이션 개발을 위한 글을 올리기로 했습니다.


지금 하는 TDD 일을 계속 할 지 아니면 Android App 개발 팀으로 옮길지 아직 모르는데요.

다시 Android 개발 관련해서 이야기가 나오니까 준비도 하고 싶고 또 개발도 하고 싶고 하네요.


당분간 Native Android App 개발 관련해서 공부를 할 것 같습니다.


우선 개발 환경부터 구축을 해 보겠습니다.


아래 싸이트로 가서 Android SDK 툴을 다운 받으세요.


http://developer.android.com/sdk/index.html




32bit 64bit 둘 중 하나 선택해서 다운 받으시면 됩니다.


다운 다 받은 후 적당한 곳에 압축을 풀어 주세요.




압축 푼 다음에 SDK Manager 실행을 하세요.




저는 모든 것 다 선택하고 Install Package 했어요. 그거 누르면 License 에 동의하겠느냐고 물어오는데 동의 한다고 한 다음에 Install 버튼 누르시면 됩니다.




Install 이 다 됐으면 이제 eclips 를 실행하시면 됩니다.



이제 HelloWorld 앱을 하나 만들어 보겠습니다.




New - New Android Application을 선택하시고 Application Name 에는 아무 이름이나 넣으세요. 그리고 Next 버튼을 누르세요.

다음 나오는 화면에서도 계속 Next 버튼을 누르세요.


Configure Project - Next
Configure Launcher Icon - Next
Create Activity - Next




그러면 이와 같이 애플리케이션이 만들어 집니다.


이 앱은 화면에 HelloWorld 를 display 해 주는 앱입니다.


이제 이 앱을 한번 실행해 봐야 할 텐데요.

에뮬레이션을 실행하도록 하겠습니다.




우선 에뮬레이터인 Android Vertual Device를 만들어야 하는데요.

메뉴바 밑에 있는 아이콘들 중 핸드폰 모양의 아이콘을 클릭하시면 위 화면이 나옵니다.

여기서 New를 누르시고 적당한 모델을 선택하시면 됩니다.


그리고 나서 Cntl-11 을 누르거나 Run을 누르시면 되는데요.


저는 Failed to allocate memory: 8 이런 메세지가 뜨네요.


이런 경우 해당 avd 폴더로 가서 config.ini 파일을 열어보세요.



이 파일은 C:\Users\본인\.android\avd\test.avd 경로에 있습니다.




빨간 줄 있는 부분이 1024MB가 아니라 1024로 돼 있을 겁니다.

만역 그렇다면 위에 보이는 것 처럼 고쳐 주세요.


그리고 나서 Run 을 실행하시면 됩니다.


에뮬레이터로 하면 시간이 조금 걸리는데요.

실제 개발 하실 때는 에뮬레이터 보다는 디바이스로 실행시켜서 테스트 해 보시는게 좋을 겁니다.





반응형

Comment

  1. 알 수 없는 사용자 2013.11.03 19:49

    좋은글 잘보고 갑니다.

이전 1 2 3 4 5 다음