IT_Programming/Android_Java

TypeViewManager

JJun ™ 2012. 6. 20. 14:13



 출처: http://blog.chopestory.net/201



이런 저런 앱을 만들다 보니 activity전환 속도나 광고 노출 유지를 위해서 하나의 Activity에서 여러개의 다른 뷰를 보여줄 필요가 있었습니다.

기존에 제공되던 ViewFlipper를 관리할 수 있는 Manager를 제작하였습니다. ViewFlipper가 순서대로 동작되는 단점이 있습니다. 이 점을 극복하기 위해 Manager는 지정된 View로 이동시 필요한 만큼의 이동을 하게 됩니다.

사용하는 방법은 간단 합니다. ViewFlipper 안에 어떤 뷰가 순서대로 적용되어 있는지 알아야 합니다. 그리고 그 순서에 맞게 type을 등록하면 됩니다.

간단한 예제로 보는게 빠를 것 입니다. 간단하게 TabView형식으로 구현한 예제입니다.


main.xml

1
2
3
4
5
6
7
8
9
10
11
<!--?xml version="1.0" encoding="utf-8"?-->
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical">
    <linearlayout android:id="@+id/bottomMenu" android:layout_width="fill_parent" android:layout_height="80dp" android:layout_alignparentbottom="true">
        <textview android:id="@+id/menuA" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:layout_gravity="center" android:background="#FF0000" android:textcolor="#000000" android:textsize="50dp" android:text="View 1">
        <textview android:id="@+id/menuB" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:layout_gravity="center" android:background="#00FF00" android:textcolor="#000000" android:textsize="50dp" android:text="View 2">
    </textview></textview></linearlayout>
    <viewflipper android:id="@+id/viewFlipper" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_alignparenttop="true" android:layout_above="@id/bottomMenu">
        <textview android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFFF00" android:textcolor="#000000" android:textsize="50dp" android:text="View 1">
        <textview android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#00FFFF" android:textcolor="#000000" android:textsize="50dp" android:text="View 2">
    </textview></textview></viewflipper>
</relativelayout>

TypeManagerTestActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.yhg.test.typemanager;
 
import yhg.library.view.manager.TypeViewManager;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ViewFlipper;
 
public class TypeManagerTestActivity extends Activity {
    public static final String TYPE_A = "a";
    public static final String TYPE_B = "b";
     
    protected TypeViewManager mManager;
     
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
         
        ViewFlipper viewFlipper = (ViewFlipper) findViewById(R.id.viewFlipper);
        mManager = new TypeViewManager(viewFlipper);
        mManager.addType(TYPE_A);
        mManager.addType(TYPE_B);
        mManager.setStartType(TYPE_A);
         
        View menuA = findViewById(R.id.menuA);
        View menuB = findViewById(R.id.menuB);
         
        menuA.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                mManager.showTypeView(TYPE_A);
            }
        });
         
        menuB.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                mManager.showTypeView(TYPE_B);
            }
        });
    }
}

사용법은 위와 같습니다. 간단합니다. 실제로 어떻게 구동되는지는 아래 동영상을 참고하세요.



데모 동영상을 한번에 찍다 보니.... 약간 엉성하네요 하하하하

정말 간단한 기능이고 TypeViewManager에도 별로 대단할 것 없습니다. 내부 코드는 다음과 같습니다.


[TypeViewDistance.java]


package yhg.library.view.manager;
 
/**
 * Type끼리의 거리를 저장
 * @author yoonhg84
 */
public class TypeViewDistance {
    /**
     * 왼쪽 방향을 나타내는 상수
     */
    public final static int LEFT = -1;
    /**
     * 방향이 설정되어 있지 않은 상태
     */
    public final static int NONE = 0;
    /**
     * 오른쪽 방향을 나타내는 상수
     */
    public final static int RIGHT = 1;
     
    /**
     * 현재의 방향
     */
    private int direction;
    /**
     * Type에서 Type까지의 거리
     */
    private int distance;
     
    /**
     * 생성자로 기본 정보를 설정한다
     * @param dir   방향 상수
     * @param dis   거리
     */
    public TypeViewDistance(int dir, int dis){
        this.direction = dir;
        this.distance = dis;
    }
     
    /**
     * 방향 정보를 얻는다.
     * @return 방향 상수
     */
    public int getDirection(){
        return direction;
    }
 
    /**
     * Type에서 Type까지의 거리를 얻는다.
     * @return 거리
     */
    public int getDistance(){
        return distance;
    }
}


[TypeViewManager.java]

 
package yhg.library.typeview.manager;

import java.util.ArrayList;
import java.util.Stack;
 
import android.util.Log;
import android.widget.ViewFlipper;
 
/**
 * TypeViewManager는 ViewFlipper를 이용하여 하나의 Activity에서 여러개의 뷰를 전환할 수 있도록 관리해 준다.
 * 추가되는 Type만 관리되며 나머지 처리는 외부에서 구현하여야 한다.
 *
 * @author yoonhg84
 */
public class TypeViewManager {
    /**
     * TypeViewManager가 관리하게 될 ViewFlipper
     */
    private ViewFlipper mFlipper;
    /**
     * Type 을 저장하는 list
     */
    private ArrayList<string> mTypeList;
    /**
     * 지정된 Type에서 메뉴가 보여야 하는지를 저장하는 list
     * index값은 typeList와 동일하다
     */
    private ArrayList<boolean> mMenuList;
    /**
     * Type이 변경될때 기록되는 log
     */
    private Stack<integer> mLog;
    /**
     * 현재 type
     */
    private int mType;
    /**
     * 이전 type
     */
    private int mPreType;
    /**
     * 시작하는 type
     */
    private int mStartType;
     
    /**
     * 수행에 필요한 변수들을 초기화한다
     *
     * @param flipper   가장 핵심이 되는 플립퍼
     */
    public TypeViewManager(ViewFlipper flipper){
        this.mFlipper = flipper;
        this.mTypeList = new ArrayList<string>();
        this.mMenuList = new ArrayList<boolean>();
        this.mPreType = -1;
        this.mType = 0;
        this.mStartType = 0;
         
        initLog();
        mLog.push(mStartType);
    }
     
    /**
     * 시작 타입을 설정한다
     * 반드시 초기화->타입추가->시작타입 순으로 진행되어야 한다.
     * 시작타입으로 지정할 타입의 추가가 뒤에 오면 오류 발생!!
     *
     * @param name  시작 타입으로 지정할 타입명
     */
    public void setStartType(String name){
        mStartType = getType(name);
        initLog();
        mLog.push(mStartType);
    }
     
    /**
     * 로그를 초기화 한다
     */
    private void initLog(){
        this.mLog = new Stack<integer>();
    }
     
    /**
     * 타입명으로 새로운 타입을 추가한다
     *
     * @param name  추가할 타입명
     * @return 추가된 타입 번호
     */
    public int addType(String name){
        mTypeList.add(name);
        mMenuList.add(true);
        return getType(name);
    }
     
    /**
     * 타입명으로 타입 번호를 구한다
     *
     * @param name  번호를 구하고 싶은 타입 이름
     * @return 타입 번호
     */
    private int getType(String name){
        return mTypeList.indexOf(name);
    }
     
    /**
     * 타입 번호로 타입명을 얻는다
     *
     * @param type  이름을 구하고 싶은 타입 번호
     * @return 타입명
     */
    private String getTypeName(int type){
        return mTypeList.get(type);
    }
     
    /**
     * 현재 타입의 이름을 구한다
     *
     * @return 현재 타입의 이름
     */
    public String getCurrentTypeName(){
        return mTypeList.get(mType);
    }
     
    /**
     * 이전 타입의 이름을 구한다.
     * @return 이전 타입이 있다면 타입이름을 리턴, 이전 타입이 없
     */
    public String getPrevTypeName(){
        if(mPreType == -1){
            return null;
        }
        return mTypeList.get(mPreType);
    }
     
    /**
     * 타입의 갯수를 구한다
     *
     * @return 타입 갯수
     */
    private int getNumOfType(){
        return mTypeList.size();
    }
     
    /**
     * 현재의 타입에서 메뉴가 보여야 하는지를 구한다
     *
     * @param type  메뉴의 보임 여부를 확인하고 싶은 타입
     * @return 메뉴가 보여야 하면 true 아니면 false
     */
    private boolean getMenuVisible(int type){
        return mMenuList.get(type);
    }
     
    /**
     * 메뉴 보임 여부를 설정한다
     *
     * @param name      설정할 타임명
     * @param visible   보임 여부
     */
    public void setMenuVisible(String name, boolean visible){
        mMenuList.set(getType(name), visible);
    }
     
    /**
     * 타입과 타입 사이에 가장 가까운 거리와 방향을 구한다
     *
     * @param t1    비교할 타입1
     * @param t2    비교할 타입2
     * @return 방향과 거리가 저장되는 TypeViewDistance를 리턴한다. 방향은 LEFT,NONE,RIGTH가 존재한다
     */
    private TypeViewDistance getDistance(int t1, int t2){
        int tmp;
        int direction = TypeViewDistance.NONE;
        int distance = 0;
        int large, small;
 
        if(t1 > t2){
            large = t1;
            small = t2;
            direction = TypeViewDistance.LEFT;
        }
        else if(t1 < t2){
            large = t2;
            small = t1;
            direction = TypeViewDistance.RIGHT;
        }
        else
            return new TypeViewDistance(direction,distance);
         
        distance = large - small;
        //tmp = (getNumOfType() + small + 1) - large;
        tmp = getNumOfType() - distance;
         
        if(tmp < distance){
            distance = tmp;
            direction = (direction == TypeViewDistance.LEFT) ? TypeViewDistance.RIGHT : TypeViewDistance.LEFT;
        }
         
        return new TypeViewDistance(direction,distance);
    }
     
    /**
     * 보여지는 타입의 로그를 저장한다
     * 시작 타입이 오게 되면 이전 로그 기록은 중요하지 않으므로 초기화되고 시작타입만 들어가게 된다.
     *
     * @param type  변경 전 타입
     */
    private void addLog(int type){
        int count = log.search(type);
         
        if(log.size() == 0){
            initLog();
        }
        else if(count != -1){
            for(int i=0; i < count; i++){
                log.pop();
            }
        }
         
        log.push(type);
    }
     
    /**
     * 가장 최근에 추가된 로그를 제거한다.
     * 순서가 꼬이는 문제를 해결하기 위함
     * @param type 지정된 type이 최근 로그일 경우 삭
     */
    public void removeTopLog(String typeName){
        int type = getType(typeName);
        int count = log.search(type);
         
        if(log.size() == 0){
            initLog();
        }
        else if(count == (log.size()-1)){
            log.pop();
        }
    }
     
    /**
     * BACK 키를 눌렀을 경우 타입뷰에서 처리하는 이벤트
     * 시작 타입이면 false를 리턴하여 액티비티에서 처리하게 한다
     * 시작 타입이 아니라면 로그에서 정보를 가져와서 전 타입뷰로 이동한다
     *
     * @return 현재 보여지고 있는 타입이 시작 타입이라면 false, 다른 타입이라면 전 타입을 보여주고 true 리턴
     */
    public boolean onBackKeyDown(){
        int prev;
 
        // 현재의 뷰를 제거 한다
        log.pop();
         
        if(type == startType || log.size() == 0){
            return false;
        }
         
        // 이전 뷰를 구한다
        prev = log.peek();
        showTypeView(getTypeName(prev));
         
        return true;
    }
     
    private void displayLog(){
        int size = log.size();
        StringBuilder buffer = new StringBuilder();
         
        for(int i=0; i < size; i++){
            buffer.append(log.get(i)+" ");
        }
         
        Log.i("cauin",buffer.toString());
    }
     
    /**
     * 메뉴키 이벤트를 처리한다.
     * 현재 타입에서 보여야 할지 안 보여야 할지 알려 준다
     *
     * @return 화면에 보여야 하면 false, 보이지 말아야 하면 true 이다
     */
    public boolean onMenuKeyDown(){
        if(getMenuVisible(type) == true)
            return false;
         
        return true;
    }
     
    /**
     * Flipper를 사용하여 화면을 전환한다
     * 현재 모드와 원하는 모드의 값을 계산하여 자동으로 이동시켜 준다.
     *
     * @param need  보여주고 싶은 모드, 즉 보여주고 싶은 View
     */
    public void showTypeView(String needName){
        int need = getType(needName);
         
        if(type == need)
            return ;
         
        TypeViewDistance tvd = getDistance(type,need);
        int distance = tvd.getDistance();
         
        if(tvd.getDirection() == -1){
            for(int i=0; i < distance; i++)
                flipper.showPrevious();
        }
        else{
            for(int i=0; i < distance; i++)
                flipper.showNext();
        }
 
        type = need;
        addLog(type);
    }
}



다음에는 view 변경시 이벤트 처리를 하도록 해야겠습니다.

정리를 해보려고 해도 글은 적게 써지네요.....;;