ViewPager와 PageAdapter

컴퓨터공부/안드로이드 2019.03.05 23:38 Posted by 아는 개발자 아는 개발자

0. ViewPager와 PagerAdapter


ViewPager와 PagerAdapter는 아래 네이버 앱의 뉴스, 연애 스포츠 탭처럼 큰 화면 안에서 여러 개의 작은 화면을 좌우로 움직이는 UI를 구성할 때 사용하는 안드로이드 라이브러리다. ViewPager는 TextView, Button과 같이 화면의 인터페이스로 사용자가 조작할 수 있는 컴포넌트고 PagerAdapter는 ViewPager가 생성한 공간을 채우는 데이터 공간으로 보면 된다. 


특별히 화면에 이펙트를 넣을 생각이 없으면 ViewPager는 안드로이드에서 제공하는 라이브러리를 그대로 쓰고 내가 넣고 싶은 화면을 담은 별도의 PagerAdapter를 만들어서 ViewPager에 연결 시키면 된다.


이버의 뉴스, 연예, 스포츠 탭은 ViewPager와 PagerAdapter로 만들었을 것 같다. (아닐수도 있고)


1. Fragment + PagerAdapter 


큰 화면의 단위는 Activity이고 작은 화면의 단위는 주로 Fragment를 사용하기 때문에 안드로이드에서는 PagerAdater를 Fragment를 사용해서 구현하기 쉽도록 FragmentPagerAdatper를 만들어 뒀다. 이것과 이름이 비슷한 FragmentStatePagerAdapter가 있는데 Fragment 메모리 관리를 어떤식으로 할 것인가에 따라서 조금 차이가 난다. 깊게 들어가면 어려울 수 있으니 간단하게 ViewPager가 관리하는 페이지가 얼마 되지 않는 경우에는 FragmentPagerAdapter를, 많으면 FragmentStatePagerAdapter를 사용하도록 하자. 이 둘로 나눴지만 상속해서 작성하게될 Custom FragmentPagerAdatper의 코드상의 차이는 거의 없다.


2. Custom FragmentPagerAdapter


앞서 말한대로 여러 개의 화면을 좌우로 슬라이드 할 수 있는 화면을 만들기 위해선 별도의 PageAdapter가 필요하다. 안드로이드에서 FragmentPagerAdapter 클래스를 만들어 뒀으니 이를 상속해서 Custom PageAdapter를 만들 수 있다. 몇가지 오버라이드 함수들만 살펴보자.


public Fragment getItem(int position)


n번째 포지션의 Fragment Item 오브젝트 정보를 리턴한다. 채울 화면의 순서를 변경하고 싶다면 여기서 return 하는 Fragment 객체를 position 에 따라서 변경하면 된다. 반드시 구현해야하는 함수다.

    @Override
    public Fragment getItem(int position) {
        Fragment tab = null;

        switch (position) {
        case 0:
            tab = new PreviewTab();
            break;
        case 1:
            tab = new CodeTab();
            break;
        }

        return tab;
    }


public int getCount()


ViewPager에 넣을 화면의 개수를 리턴하는 함수다. 이것도 반드시 구현해야 하는 함수다.

    @Override
    public int getCount() {
        return this.mNumOfTabs;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mTitles[position];
    }


public Object instantiateItem


View에 넣을 Page 객체를 리턴해주는 작업. 이미 FragmentPagerAdapter에서 구현을 해뒀기 때문에 따로 오버라이드 할 필요는 없다. 커스텀한 fragment를 사용할 수도 있긴 할텐데 가능하면 부모 함수를 바로 쓰는 것을 추천한다.

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        registeredTabs.put(position, (IMarkdownTab)fragment);
        return fragment;
    }


public void destroyItem


생성한 Fragment를 제거하는 작업이다. instantiateItem 함수에서 부모 함수를 이용했다면 destroyItem 에서도 부모 함수를 이용해서 처리하는 것이 좋다. 그래야만 부모 함수의 변수들과 싱크를 맞출 수 있으니까. 마찬가지로 가능하면 부모 함수를 바로 쓰는 것을 추천한다.

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
    }


전체 코드는 여기서 볼 수 있다.


3. ViewPager + Custom PageAdapter 


이제 만든 CustomPageAdapter를 xml의 ViewPager과 연결하면 된다. 아래와 같이 adapter 객체를 선언해준 후 ViewPager의 setAdapter 함수를 통해 연결 해줄 수 있다. 직관적인 코드라 이해하는데 별로 어렵지 않을 것 같다


adapter = new MarkdownPagerAdapter(getSupportFragmentManager(), Titles,
        selectedImgSrc, unselectedImgSrc, Titles.length);
viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(adapter);


4. ViewPager 페이지 선택 콜백함수


ViewPager 객체 내에 PageChange 리스너를 등록해서 페이지가 변경 될 때마다 특정한 작업을 하도록 만들 수 있다.


viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int curPosition) {
        IMarkdownTab curImt = adapter.getRegisteredTab(curPosition);
        IMarkdownTab preImt = adapter.getRegisteredTab(prePosition);

        if (curPosition == prePosition || curImt == null)
            return;

        if (preImt!=null)
            preImt.cbPageUnSelected();

        curImt.cbPageSelected();
        prePosition = curPosition;
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});


이또한 함수명으로 충분히 언제 불리는지 이해할 수 있을 것 같다. onPageSelected 함수에 넣은 코드는 사용자가 선택한 페이지의 화면을 새로고침 하도록 만든 작업이다.  상황에 맞춰서 커스터마이즈하면 된다. 아 그리고 리스너는 여러 개를 등록 할 수 있다. 난 이 점을 이용해 아래 그림처럼 화면 포커스를 바꿀 때 새로고침과 동시에 바의 위치와 그림을 바꾸기도 했다.      

     

   

리스너를 이용해 화면 포커스가 바뀔 때 그림 이미지와 바의 위치를 변경 할 수 있었다

'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

안드로이드 Service  (0) 2019.03.19
AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26

px, dp, sp 개념 정리

컴퓨터공부/안드로이드 2019.02.16 16:04 Posted by 아는 개발자 아는 개발자

XML 파일 안에서 UI 컴포넌트의 크기를 정할 때 dp, sp, px 단위의 원리를 제대로 알지 않고 사용하는 경우가 있는데 이렇게 막 사용하다 보면 디바이스 해상도가 변경 될 때 화면이 깨지는 경우가 생긴다. 이번 포스트에서는 지금까지 막무가내로 사용해온(?) dp, sp, px 단위를 총 정리 해보려고 한다.


0. px (Pixel)



주소화 할 수 있는 화면의 가장 작은 단위. 점을 찍을 수 있는 가장 작은 단위라고 생각하면 된다. 화면 해상도와 관계 없이 컴포넌트의 크기를 절대값으로 세팅할 수 있으며 위의 그림으로 알 수 있듯이 오른쪽으로 갈 수록 폰의 해상도가 높아지면서 텍스트 상자와 이미지 아이콘이 작아진 것을 알 수 있는데 값을 픽셀 단위로 설정했기 때문에 그렇다. 이런 단위로는 여러 디바이스 환경마다 아이콘의 크기가 천차만별이 될 수 있어서 안드로이드 환경에서는 잘 사용하지 않는다.


1. dp (Density-independent Pixels)


화면의 해상도값에 따라서 크기가 조정되는 단위다. 160dpi 스크린 해상도에서는 1px만큼의 크기를 가지고 해상도 값이 커질수록 픽셀의 값이 커진다. 320dpi 해상도면 2px 만큼의 크기를 가지고 480dpi 면 3px만큼 가지는 원리다. 위의 그림에서도 보이듯이 해상도가 높아져도 텍스트상자와 이미지의 크기가 일정한 것을 알 수 있는데 이는 해상도에 따라서 크기가 재설정 됐기 때문이다. 해상도에 따라 크기가 자동으로 늘어나고 줄어들 수도 있으니 스펙이 다양한 안드로이드 디바이스 환경에서 적용할 때 유용한 단위다.


2.  sp (Scale-independent Pixels)


dp랑 비슷한 원리로 동작하며 사용자의 글짜 크기(font-size)를 조정할 때 사용되는 단위다. 

'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

Device screen dpi 값에 따라 처리하기

컴퓨터공부/안드로이드 2019.02.16 14:46 Posted by 아는 개발자 아는 개발자

안드로이드 앱을 만들때면 무수히 많은 종류의 화면과 모두 호환 할 수 있는 UI를 만드는 일이 꽤나 골칫 거리다. 아이폰과 달리 안드로이드 폰은 제조사가 한군데만 있는 것도 아니고 또 같은 회사에서 만든 폰이라도 디스플레이 스펙이 제각각이라 이에대한 고려 없이 막만들었다간 화면이 깨져버리는 참사가 벌어지고 만다.


Android에서는 UI 개발자들이 이런 점들에 대해서 유연하게 대처 할 수 있도록 여러가지 API를 제공하는데 이번 포스트에서는 getResources().getDisplayMetrics().density 에 대해서 알아보려고 한다.


이 값은 디바이스의 160dpi 대비 디바이스의 screen density (dpi) 의 비율을 나타내며 실행되고 있는 디바이스 화면 스펙별로 값이 다르다. 디바이스의 화면이 320dpi면 2이고 420dpi면 2.625의 값을 가진다. 이 비율 값을 이용하면 다양한 하드웨어 스펙 별로 화면 값을 자동으로 조정 할 수 있게 된다.


int padding = (int) (TAB_IMAGE_VIEW_PADDING_PIV 
       / (getResources().getDisplayMetrics().density * 2));
imageView.setPadding(padding, padding, padding, padding);


이 값을 이용해 애플리케이션 오른쪽 상단 아이콘의 image view 주변 패딩 값을 조절하는데 사용 해봤다. View 클래스의 public void setPadding 함수는 인자를 절대 수치인 pixel 단위로 값을 받기 때문에 변환된 값을 전달해야한다. 그래서 getResources().getDisplayMetrics().density 값을 나눠서 화면 스크린 화질이 좋을 수록 값이 작아지도록 변경했다. 결과 아래 그림처럼 다른 화질을 가진 환경에서도 아이콘의 크기를 맞춰줄 수 있게 됐다.


'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

안드로이드 선 추가하기

컴퓨터공부/안드로이드 2019.01.26 15:19 Posted by 아는 개발자 아는 개발자

안드로이드 앱을 사용하다보면 기사나 댓글 화면에는 여러 개의 아이템을 구분짓기 위해서 빨간 상자로 표시한 것처럼 선을 사용한다.


1. 원리


안드로이드에선 따로 선에 해당하는 라이브러리가 있지는 않고 직사각형 두개를 겹쳐서 선처럼 보이게하는 트릭을 사용한다. 아래의 그림처럼 한 사각형은 선으로 사용할 색으로 채우고 다른 사각형은 배경색 채우는 식이다. 두 사각형을 겹칠 때 흰색 사각형을 선색 사각형 위에 올리고 위치를 위쪽을 조금 올리면 선색 사각형의 남은 부분이 선처럼 보이는 착시효과를 줄 수 있다.



2. 코드


코드상으로 표현하면 다음과 같다.

// res/drawable/line.xml


    
    
        
            
            
        
    

    
    
        
            
            
        
    


Line color rectangle은 앞의 그림에서 왼쪽 사각형에 해당하는 코드고 Background rectangle은 오른쪽 사각형에 해당한다. Background rectangle 코드를 보면 android:bottom 값이 1dp 가 설정돼있는데 이것은 사각형을 아래에서 1dp만큼 띄어두겠다는 뜻이다. 


각 사각형마다 stroke와 solid 속성값이 있다. stroke 는 선의 색을 의미하고 solid는 사각형 내부 색을 말한다. 왼쪽 사각형은 선과 내부 색이 회색 int 값인 #FF5E5E5E 으로, 왼쪽 사각형은 흰색인 #FFFFFFFF 으로 채워져있다.


3. 적용


완성한 xml 파일을 선을 표시할 영역의 backgroud로 설정하면 아래와 같이 적용된다. 타이틀 텍스트 뷰 밑에 선을 넣고자 LinearLayout의 background 값으로 설정했다.




        
    



'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

SharedPreference

컴퓨터공부/안드로이드 2019.01.10 21:52 Posted by 아는 개발자 아는 개발자

애플리케이션을 종료한 이후에도 수정한 값을 유지하려면 데이터를 간단한 텍스트 파일에 써두거나 특정 데이터베이스의 형태로 저장해야 한다. 안드로이드에서는 관계형 데이터베이스로는 SQLite를, 그리고 단순히 key-value 값을 저장할 수 있는 용도인 Shared Preference를 제공한다.


Shared Preference 


애플리케이션의 설정 값처럼 데이터가 key-value의 형태이고 단순한 타입을 사용하며(boolean, int, float, long, string) 기록해야할 양이 얼마 되지 않는다면 관계형 데이터베이스를 사용하는 것보다 SharedPreference를 쓰는 것이 훨씬 편하다. SharedPreferece는 복잡하고 길고긴 쿼리문 없이 객체와 key값을 이용해 데이터를 쉽게 수정하고 가져올 수 있다.


1. 객체 생성


settings = Context.getSharedPreferences(PREF_NAME, MODE);


데이터를 수정할 수 있는 SharedPreference 객체는 Context 객체내에 내장된 getSharedPreference 함수를 통해 가져올 수 있다. 상황에 따라서 다르나 나는 주로 getApplicationContext를 사용한다.


인자 값으로는 두개를 받는데 첫번째는 PREF_NAME, SharedPreference로 저장되는 데이터베이스의 이름을 말한다. 이 값이 달라지면 서로 다른 데이터베이스 파일에 저장되게 되고 별도의 key값으로 관리된다. PREF_NAME은 storage에 XML파일의 이름으로 들어간다.


두번째 인자 값은 동작하는 형태다. 주로 0을 넣거나 MODE_PRIVATE, MODE_WORLD_READABLE, MODE_WORLD_WRITEABLE을 혼합해서 사용한다.


2. 기록


SharedPreferences.Editor editor = settings.edit();
editor.putLong(FILE_ID, fileId);
editor.commit();


값을 기록할 때는 생성한 SharedPreference 객체로부터 editor 객체를 가져온다. 이 객체내에는 putInt, putBoolean, putString ... 함수가 있는데  모두 key값에 해당하는 데이터가 없으면 데이터를 추가하고 있으면 업데이트 하는 함수다. 저장하는 데이터 타입은 함수 명으로 유추할 수 있다.


첫번쨰 인자 값은 key값이고 두번째 인자 값은 저장할 데이터의 값이다. key값은 나중에 데이터를 읽어올 때 필요하니 static 변수로 저장해두는 것이 편하다.


putXXX 함수를 호출한 다음에는 에디터 함수 내의 commit 또는 apply함수를 호출해서 데이터 값을 최종적으로 기록한다. 호출하지 않으면 수정한 값이 파일에 저장되지 않는다. commit은 동기 방식으로 apply는 비동기 방식으로 값을 업데이트하니 이 또한 상황에 따라서 적절히 활용하면 된다.


3. 읽어오기 


return settings.getLong(FILE_ID, -1);


값을 수정할 때 처럼 SharedPreference 함수에 내장된 getXXX 함수를 통해서 읽어올 수 있다. 첫번재 인자는 읽어올 key 값이고 두번째 인자는 만약 key값에 해당하는 데이터가 없거나 읽어오는데 실패했을 때 기본으로 얻게될 데이터 값이다. 


4. 주의사항


공식문서에는 SharedPreference의 동작 형태중 하나인 MODE_MULTI_PROCESS 가 API23 이후로 Deprecated 됐고 앞으로 안드로이드에서도 지원할 생각이 없다고 한다.


MODE_MULTI_PROCESS 에 해당하는 대표적인 예로 안드로이드 UI 컴포넌트와 안드로이드 서비스가 별도의 프로세스로 분리된 경우가 있다. 개발하다보면 안드로이드 액티비티와 서비스가 데이터를 공유해야하는 일이 종종 생기는데 Context와 데이터 이름만 지정하면 얻어올 수 있는 SharedPreference 객체를 사용하는 것이 가장 심플하게 구현할 수 있는 방법이라 자주 사용된다.


액티비티와 서비스가 동일한 프로세스라면 상관 없지만 둘이 별도의 프로세스인 경우엔 실행도중에 동기화 문제가 발생한다. 글쓴이가 몸소 체험한 동기화 문제는.. 서비스를 먼저 실행시킨 다음 액티비티 에서 값을 변경했더니 백그라운드 서비스에서 수정한 값을 읽어오지 않고 예전 값을 계속 읽어오던 일이 있었다. 처음에는 데이터 수정이 제대로 되지 않았는지가 의심스러워 commit() 함수 리턴값만 확인 했었는데 알고보니 별도의 프로세스 환경에서는 사용할 수 없었다. 이것 때문에 몇시간을 잡았는지 모른다.



'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10