이런 저런 앱을 만들다 보니 activity전환 속도나 광고 노출 유지를 위해서 하나의 Activity에서 여러개의 다른 뷰를 보여줄 필요가 있었습니다.
기존에 제공되던 ViewFlipper를 관리할 수 있는 Manager를 제작하였습니다. ViewFlipper가 순서대로 동작되는 단점이 있습니다. 이 점을 극복하기 위해 Manager는 지정된 View로 이동시 필요한 만큼의 이동을 하게 됩니다.
사용하는 방법은 간단 합니다. ViewFlipper 안에 어떤 뷰가 순서대로 적용되어 있는지 알아야 합니다. 그리고 그 순서에 맞게 type을 등록하면 됩니다.
간단한 예제로 보는게 빠를 것 입니다. 간단하게 TabView형식으로 구현한 예제입니다.
main.xml
TypeManagerTestActivity.java
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에도 별로 대단할 것 없습니다. 내부 코드는 다음과 같습니다.
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;
}
}
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 mTypeList;
/**
* 지정된 Type에서 메뉴가 보여야 하는지를 저장하는 list
* index값은 typeList와 동일하다
*/
private ArrayList mMenuList;
/**
* Type이 변경될때 기록되는 log
*/
private Stack 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();
this.mMenuList = new ArrayList();
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();
}
/**
* 타입명으로 새로운 타입을 추가한다
*
* @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);
}
}