사용자 삽입 이미지사용자 삽입 이미지사용자 삽입 이미지


사용자로 하여금 리스트에 표시된 데이터의 위치를 변경할 수 있도록 하고싶은데 편집을 사용하도록 하기에는 부담이 따른다면(혹은 숨겨진 기능으로) LongClick을 이용해서 리스트의 순서를 변경하도록 하는 방법을 생각 할 수 있다.

https://github.com/commonsguy/cwac-touchlist를 참고로 만들보았다.

TouchListView.java

public class TouchListView extends ListView {
	private static final int SCROLLPOINT = 30;
	private ImageView mDragView;
	private WindowManager.LayoutParams mWindowParams;
	private WindowManager mWindowManager;
	private int currentPosition;
	private int viewCenter;
	private DropListener dragListener;
	private RemoveListener removeListener;
	private Bitmap dragBitmap;
	private boolean moveMode = false;

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

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

	public TouchListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}
	
	// windowManager 를 초기화 합니다.
	private void init() {
		mWindowParams = new WindowManager.LayoutParams();
		mWindowParams.gravity = Gravity.TOP;
		mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
		mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
		mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
				| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
				| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
				| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
		mWindowParams.format = PixelFormat.TRANSLUCENT;
		mWindowParams.windowAnimations = 0;

		mWindowManager = (WindowManager) getContext()
				.getSystemService("window");
	}
	
	public void setDragListener(DropListener dragListener) {
		this.dragListener = dragListener;
	}

	public void setRemoveListener(RemoveListener removeListener) {
		this.removeListener = removeListener;
	}

	//이동모드인지 스크롤 모드인지를 입력받는다.
	public void setMoveMode(boolean moveMode){
		this.moveMode = moveMode;
	}
	
	//이동 모드를 시작한다.
	public void start(int position){
		if (position == AdapterView.INVALID_POSITION) {
			return;
		}
		TextView child = (TextView) getChildAt(position - getFirstVisiblePosition());
		
		child.setTextColor(Color.WHITE);
		child.setDrawingCacheEnabled(true);
		viewCenter = child.getHeight() / 2;
		stopDragging();
		startDragging(0, child.getHeight()/2+child.getTop(), child);
		currentPosition = position;
		removeListener.remove(position);
	}
	
	
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (moveMode) {
			int x = (int) ev.getX();
			int y = (int) ev.getY();
			switch (ev.getAction()) {
			
				case MotionEvent.ACTION_UP: {
					stopDragging();
					moveMode = false;
					if (dragListener != null) {
						//터치위치를 찾아 셀의 포지션을 넘긴다.
						int position = pointToPosition(x, y);
						if (position == AdapterView.INVALID_POSITION) {
							break;
						}
						View currentView = getChildAt(position
								- getFirstVisiblePosition());
						//터치위치의 중앙을 기준으로 높으면 현위치를 작으면 아래의 인덱스를 넘긴다.
						int dropPosition = (currentView.getHeight() / 2)
								+ currentView.getTop() > y ? position
								: position + 1;
						dragListener.drop(dropPosition);
					}
					break;
				}
	
				case MotionEvent.ACTION_CANCEL: {
					for (int i = getFirstVisiblePosition(); i
							+ getFirstVisiblePosition() < getLastVisiblePosition(); i++) {
						getChildAt(i - getFirstVisiblePosition()).setVisibility(
								View.VISIBLE);
					}
					stopDragging();
					moveMode = false;
				}
	
				case MotionEvent.ACTION_MOVE: {
					mWindowParams.y = y + viewCenter;
					if (mDragView == null) {
						break;
					}
					mWindowManager.updateViewLayout(mDragView, mWindowParams);
					int position = pointToPosition(x, y);
					if (position == AdapterView.INVALID_POSITION) {
						break;
					}
					if (position != currentPosition) {
						currentPosition = position;
					}
	
					int speed = 0;
					//터치위치를 기준으로 스크롤 여부와 속도를 결정한다.
					if (y > getBottom() - SCROLLPOINT) {
						speed = ((y - getBottom() + SCROLLPOINT) / 2);
					} else if (y < getTop() + SCROLLPOINT) {
						speed = ((y - getTop() - SCROLLPOINT) / 2);
					}
					
					if (speed != 0) {
						int ref = pointToPosition(0, getHeight() / 2);
						if (ref == AdapterView.INVALID_POSITION) {
							ref = pointToPosition(0, getHeight() / 2
									+ getDividerHeight());
						}
						View v = getChildAt(ref - getFirstVisiblePosition());
						if (v != null) {
							int pos = v.getTop();
							setSelectionFromTop(ref, pos - speed);
						}
						scroll(speed);
					}
				}
			}
			return true;
		}
		return super.onTouchEvent(ev);
	}
	//속도에 맞추어 스크롤을 움직인다.
	private void scroll(int speed) {
		int ref = pointToPosition(0, getHeight() / 2);
		if (ref == AdapterView.INVALID_POSITION) {
			ref = pointToPosition(0, getHeight() / 2 + getDividerHeight());
		}
		View v = getChildAt(ref - getFirstVisiblePosition());
		if (v != null) {
			int pos = v.getTop();
			setSelectionFromTop(ref, pos - speed);
		}
	}

	private void stopDragging() {
		if (dragBitmap != null) {
			dragBitmap.recycle();
			dragBitmap = null;
		}
		if (mDragView != null) {
			WindowManager wm = (WindowManager) getContext().getSystemService(
					"window");
			wm.removeView(mDragView);
			mDragView.setImageDrawable(null);
			mDragView = null;
		}
	}

	private void startDragging(int x, int y, View dragView) {
		dragBitmap = Bitmap.createBitmap(dragView.getDrawingCache());
		mDragView = new ImageView(getContext());
		mDragView.setAlpha(190);
		mDragView.setImageBitmap(dragBitmap);
		mWindowParams.x = x;
		mWindowParams.y = y;
		mWindowManager.addView(mDragView, mWindowParams);
	}

	public interface DropListener {
		void drop(int position);
	}

	public interface RemoveListener {
		void remove(int which);
	}
}

TestActivity.java
public class TestActivity extends Activity implements DropListener,RemoveListener  {
	private static String[] items = { "lorem", "ipsum", "dolor", "sit", "amet",
			"consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula",
			"vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat",
			"placerat", "ante", "porttitor", "sodales", "pellentesque",
			"augue", "purus" };

	private ArrayList<String> array = new ArrayList<String>(Arrays
			.asList(items));
	ArrayAdapter<String> aa;
	TouchListView tlv;
	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		setContentView(R.layout.main);
		aa = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,array);
		tlv = (TouchListView) findViewById(R.id.list);
		tlv.setAdapter(aa);
		tlv.setDragListener(this);
		tlv.setRemoveListener(this);
		tlv.setOnItemLongClickListener(new OnItemLongClickListener() {
			@Override
			public boolean onItemLongClick(AdapterView<?> arg0, View arg1,
					int arg2, long arg3) {
				tlv.setMoveMode(true);
				tlv.start(arg2);
				return true;
			}
		});
	}
	
	String dragData;
	
	@Override
	public void drop(int position) {
		aa.insert(dragData, position);
		aa.notifyDataSetChanged();
	}
	
	@Override
	public void remove(int which) {
		dragData = aa.getItem(which);
		aa.remove(dragData);
		aa.notifyDataSetChanged();
	}
	

}
2011/06/26 21:14 2011/06/26 21:14

안드로이드에서 맵관련 액티비티를 만들다보면 GPS정보를 가져와야 할때가 많은데 현재 GPS정보를 가져올 수 있는지 여부를 확인하기 위해서는 아래와 같이 사용하면 된다.

String context = Context.LOCATION_SERVICE;

LocationManager locationManager = (LocationManager)getSystemService(context);
boolean b =locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

마지막 열의 boolean값으로 GPS사용가능 여부를 반환한다.

GPS정보사용할수 없을때 사용자를 바로 GPS설정화면으로 보내기 위해서는 아래와 같이 인텐트를 호출 하면 된다.
Intent intent = new Intent(android.provider.Settings.ACTION_LOCATION_SETTINGS);
startActivity(intent);
2011/04/18 18:53 2011/04/18 18:53
tags: ,
UI를 만들다보면 웹으로 만드는게 더편하겠다라는 느낌이 올때가 있는데 그럴때는 webview의 loadData메소드를 이용한다. 크기를 지정할때 %기호를 많이사용하는데 loadData에서 %기호를 사용하기 위해서는 %25를 사용해야한다.
loadData를 사용하다 '웹페이지를 표시할수 없습니다' 라는 메세지와 함께 오류가 나온다면 %기호를 %25로 바꾸면 잘되는것을 볼 수 있다.

2011/04/01 14:52 2011/04/01 14:52
tags: , ,
안드로이드에서 어플리케이션 버전정보를 가져오는 간단한 코드


PackageManager pm = this.getPackageManager();

try{

packageInfo pi = pm.getPackageInfo(
"com.test.application",PackageManager.GET_SIGNATURES);

} catch(NameNotFoundException e){

e.printStackTrace();

}



2010/10/21 12:12 2010/10/21 12:12

이번에 안드로이드 SI를 하나 진행했었는데 개발중에 가장 어려웠던 부분은 아이폰에서는 인덱스 라고 부르는 페스트 스크롤의 구현이 아니었나 싶다. 사실 구현한 지금도 이게 정답이 아닌데 라는 생각이 머릿속에서 떠나지 않는다. 왠만한 부분은 거의다 구글링의 도움으로 좀 세련되게 해결할 수 있었는데 이제 입문한지 1년이 갓 지난 나에게는 늘 내가 짠 코드에대한 의심이 떠나지 않는다.

안드로이드에서 기본적으로 패스트 스크롤을 지원하긴 하는데 디자인이 아이폰에 비해 정말 구리다. 패스트 스크롤을 오바라이딩해서 수정하는 방법도 있을듯 한데 잘모르겠어서 그냥 새로 만들기로 했다.
인덱스의 코드는 다음과 같다.

//IndexBar.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListView;
import android.widget.SectionIndexer; public class IndexBar extends View {
 private char[] l ;
 private SectionIndexer sectionIndexter = null;
 private ListView list;
 public IndexBar(Context context) {
  super(context);
  init();
 }  public IndexBar(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }  private void init() {
  l = new char[]{
    'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'
  };//index에 표시될 문자들.
  setBackgroundColor(0x44FFFFFF);
 }  public IndexBar(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  init();
 }
   public void setListView(ListView _list){
  list = _list;
  sectionIndexter =(SectionIndexer) _list.getAdapter();
 }
//간단하게 터치위치를 계산해서 섹션의 위치를 가져온다.
 public boolean onTouchEvent(MotionEvent event) {
  super.onTouchEvent(event);
  int size = getMeasuredHeight()/(l.length+1);
  int i = (int)event.getY();
  int idx = i/size;
  if(idx >= l.length){
   idx = l.length-1;
  }else if(idx < 0){
   idx = 0;
  }
  if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE){
   if(sectionIndexter == null){
    sectionIndexter = (SectionIndexer) list.getAdapter();
   }
   int position = sectionIndexter.getPositionForSection(l[idx]);
   if(position == -1){
    return true;
   }
   list.setSelection(position);
  }
  return true;
 }
//화면에 세로로 표시될 문자들을 그린다.
 protected void onDraw(Canvas canvas) {
  
  Paint paint = new Paint();
  paint.setColor(0xFFA6A9AA);
  paint.setTextSize(15);
  paint.setTextAlign(Paint.Align.CENTER);
  float widthCenter = getMeasuredWidth()/2;
  float size = getMeasuredHeight()/(l.length+1);
  for(int i = 0 ; i < l.length ; i ++){
   canvas.drawText(String.valueOf(l[i]),widthCenter , size+(i*size), paint);
  }
  super.onDraw(canvas);
 }
}

인덱스를 그리면서 제일 어려웠던건 문자들의 중앙 정렬 이었는데 수학엔 약한지라 정확한 위치를 뽑기위해 여러모로 말도안되는 계산들을 잔뜩했지만 그위치를 찾는게 쉽지 않아서 정확한 중앙정렬을 시킨지못했다. 계산하는 귀찮음 이란 ....
<!-- adapterlayout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">
  <LinearLayout android:id="@+id/section" 
  android:layout_width="fill_parent"
    android:layout_height="wrap_content"
 />
  <TextView android:id="@+id/textView" 
  android:layout_width="wrap_content"
  android:layout_height="80sp"
  android:textSize="45sp"
  />
</LinearLayout>

//CustomAdapter.java import java.util.ArrayList; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.LinearLayout; import android.widget.SectionIndexer; import android.widget.TextView; public class CustomAdapter extends BaseAdapter implements SectionIndexer {  private ArrayList<String> stringArray;  private Context context;  public CustomAdapter(Context _context,ArrayList<String> arr){   stringArray = arr;   context = _context;  }  public int getCount() {   return stringArray.size();  }  public Object getItem(int arg0) {   return stringArray.get(arg0);  }  public long getItemId(int arg0) {   return 0;  } public View getView(int position, View v, ViewGroup parent) {   LayoutInflater inflate = ((Activity) context).getLayoutInflater();   View view = (View)inflate.inflate(R.layout.adapterlayout, null);   LinearLayout header = (LinearLayout) view.findViewById(R.id.section);   String label = stringArray.get(position);   char firstChar = label.toUpperCase().charAt(0);   if(position == 0){    setSection(header, label);   }else{    String preLabel = stringArray.get(position-1);    char preFirstChar = preLabel.toUpperCase().charAt(0);    if(firstChar != preFirstChar){     setSection(header, label);    }else{     header.setVisibility(View.GONE);    }   }     TextView textView =(TextView)view.findViewById(R.id.textView);   textView.setText(label);   return view;  }

 private void setSection(LinearLayout header, String label) {   TextView text = new TextView(context);   header.setBackgroundColor(0xffaabbcc);   text.setTextColor(Color.WHITE);   text.setText(label.substring(0, 1).toUpperCase());   text.setTextSize(20);   text.setPadding(5, 0, 0, 0);   text.setGravity(Gravity.CENTER_VERTICAL);   header.addView(text);  }

/*섹션에 표시될 문자의 아스키값을 받아 해당 아스키값을 첫문자로 가지는 데이터의 위치를 반환합니다.*/  public int getPositionForSection(int section) {   if(section == 35){ //섹션의 아스키값이 35일 경우 0번째를 반환합니다. 아마 특수문자 였던듯 ㅡ.ㅡ;    return 0;   }

//전체 데이터를 순회하면서 첫문자열을 비교합니다.   for(int i = 0 ; i < stringArray.size();i++){    String l = stringArray.get(i);    char firstChar = l.toUpperCase().charAt(0);//대문자 변환후 첫번째 문자열을 가져옵니다.    if(firstChar == section){//첫번째 문자열과 섹션에 표시될 문자열을 비교합니다.     return i;//같다면 해당 인덱스를 반환합니다.    }   }   return -1;  }  public int getSectionForPosition(int arg0) {   return 0;  }  public Object[] getSections() {     return null;  } }

adapter를 만들면서 가장 고민했던 부분은 섹션을 어떻게 표시할 것인가 하는 문제였다. 정말 말도안되는 다양한 상상들을 해봤는데 리스트안에 리스트를 중첩시키는 방법이라던가 섹션을 하나의 row로 만든다던가 여러가지 고민을 했었는데 결국 row에 레이아웃을 추가해놓고 높이를 wrap_content로 주는 방법을 택했다. 섹션이 들어가야 하는 자리를 찾는 방법은 여러가지가 있겠지만 바로전 문자의 첫글자와 현재 첫글자를 비교해 그전과 다를때 현재의 첫글자를 섹션에 표시하는 방법으로 구현하였다.

<!--  main.xml -->
<?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">
  <ListView android:id="@+id/list"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
  />
  <com.test.indextest.IndexBar 
 android:id = "@+id/indexbar"
   android:layout_height="fill_parent"
   android:layout_width="26px"
   android:layout_alignParentRight="true"
 />
</RelativeLayout>

//IndexTest.java
import java.util.ArrayList; import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView; public class IndexTest extends Activity{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ListView list = (ListView)findViewById(R.id.list);
       
        ArrayList<String> stringList = setListData();
       
        CustomAdapter adapter = new CustomAdapter(this,stringList);
        list.setAdapter(adapter);
        IndexBar indexBar = (IndexBar) findViewById(R.id.indexbar);
        indexBar.setListView(list);
    }  private ArrayList<String> setListData() {
  ArrayList<String> stringList = new ArrayList<String>();
        stringList.add("above");
        stringList.add("account");
        stringList.add("address");
        stringList.add("billion");
        stringList.add("board"); 
        stringList.add("build");
        stringList.add("feature");
        stringList.add("featurette");
        stringList.add("further");
        stringList.add("fuss");
        stringList.add("fussy");
        stringList.add("have");
        stringList.add("haven");
        stringList.add("hurt");
        stringList.add("hybrid");
        stringList.add("identify");
        stringList.add("ignore");
        stringList.add("illustrate");
        stringList.add("implement");
        stringList.add("indicate");
        stringList.add("irrevocable");
        stringList.add("jacket");
        stringList.add("jackpot");
        stringList.add("jealous");
        stringList.add("judge");
        stringList.add("xerox");
        stringList.add("zone");
  return stringList;
 }
}


 정말 너무 허접한 코드라 다른 사람들에게 보여주기 좀 부끄럽지만 나도 편하게 구현하려고 아무리 구글링을 해도 못찾고 직접만들게 된지라 나처럼 헤메고 있는 사람들이 어떻게 구현해야 할지 고민 하는데 도움이 되길바라며 이렇게 올려본다.
사용자 삽입 이미지

완성된 화면

2010/10/17 23:00 2010/10/17 23:00
장소가 연대 100주년기념관이라 컨퍼런스에 간다는 느낌보다는 좀 일찍출근하는 기분으로 제 6차 칸드로이드 컨퍼런스에 다녀왔다.

각 세션의 내용도 좋았고 발표하시는 분들의 열정도 느낄수 있었데 그 열정이 전이되어 내가슴도 조금은 뜨거워 지는 듯했고 코드를 짜는 사람임에도 스마트TV나 웹OS같은 새롭게 등장하는 기술에 대해 무관심 했던 것과 안드로이드나 아이폰개발을 함에 있어서 플랫폼에 대한 이해가 아닌 그저 복사와 붙여넣기 만으로 프로그램을 완성하려했던 스스로에 대해 반성하게되었다.

컨퍼런스에 대한 자세한 내용은 아래에
http://www.kandroid.org/k6/index.php

2010/10/16 20:52 2010/10/16 20:52

인텐트로 카메라를 호출할때 MediaStore.ACTION_IMAGE_CAPTURE 를 사용하는데 외장메모리가 없을경우 앱이 종료되는 문제가 생긴다. 이 문제는 어떤 기종의 단말기를 사용하느냐에 따라 발생할 수도 있고 안할 수도 있는데 내장메모리를 SD카드처럼 사용할 수 있는지에 따라 달라진다. 실제로 갤럭시S에서는 정상적으로 작동했지만 갤럭시A에서는 오류를 발생시켰다.

카메라앱을 실행하면 SD카드가 없음을 알리는 토스트가 나오지만 앱에서 카메라를 호출했을때는 오류를 발생시키는데 정확한 이유는 잘 모르겠다. 이 문제는 SD카드가 없어서 생기는 문제이므로 카메라를 호출하기전에 SD카드의 유무를 체크함으로써 해결할 수있다.

String state = Environment.getExternalStorageState(); // 단말기의 SD카드정보를 가져온다.
boolean isSDCard = state.equals(Environment.MEDIA_MOUNTED); // SD카드가 있는지 체크한다.




2010/09/07 14:47 2010/09/07 14:47