특정 요일을 선택했으면 선택한 요일에 대해서만 알람을 울리고 선택된 요일이 없으면 설정된 시간에 한번만 알람이 울리도록 합니다. 전체적인 코드는 블로그에 올려두기 보다는 그냥 했던 방법을 적어 둘려고 합니다.


//알람 등록 및 취소

	private void registAlarm()
	{
		cancelAlarm();
		
		boolean[] week = { false, mTextRepeatSun.isSelected(), mTextRepeatMon.isSelected(), mTextRepeatTue.isSelected(),
				mTextRepeatWed.isSelected(), mTextRepeatThu.isSelected(), mTextRepeatFri.isSelected(),
				mTextRepeatSat.isSelected() };
		
		boolean isRepeat = false;
		int len = week.length;
		for (int i = 0; i < len; i++)
		{
			if (week[i])
			{
				isRepeat = true;
				break;
			}
		}

		// 알람 등록
		Intent intent = new Intent(this, AlarmReceiver.class);
		
		long triggerTime = 0;
		long intervalTime = 24 * 60 * 60 * 1000;// 24시간
		if(isRepeat)
		{
			intent.putExtra("one_time", false);
			intent.putExtra("day_of_week", week);
			PendingIntent pending = getPendingIntent(intent);
			
			triggerTime = setTriggerTime();
			
			mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime, intervalTime, pending);
		}
		else
		{
			intent.putExtra("one_time", true);
			PendingIntent pending = getPendingIntent(intent);
			
			triggerTime = setTriggerTime();
			mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, pending);
		}
		
		showToastMessage(getString(R.string.alarm_time_set_toast));
	}

	private PendingIntent getPendingIntent(Intent intent)
	{
		PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
		return pIntent;
	}
	
	private void showToastMessage(String message)
	{
		if(mToast == null)
		{
			mToast = Toast.makeText(this, message, Toast.LENGTH_SHORT);
		}
		else
		{
			mToast.setText(message);
		}
		mToast.show();
	}
	
	private long setTriggerTime()
	{
		// current Time
		long atime = System.currentTimeMillis();
		// timepicker
		Calendar curTime = Calendar.getInstance();
		curTime.set(Calendar.HOUR_OF_DAY, this.mAlarmData.getHour(this));
		curTime.set(Calendar.MINUTE, this.mAlarmData.getMinute(this));
		curTime.set(Calendar.SECOND, 0);
		curTime.set(Calendar.MILLISECOND, 0);
		long btime = curTime.getTimeInMillis();
		long triggerTime = btime;
		if (atime > btime)
			triggerTime += 1000 * 60 * 60 * 24;
		
		return triggerTime;
	}

	private void cancelAlarm()
	{
		Intent intent = new Intent(this, AlarmReceiver.class);
		PendingIntent pending = getPendingIntent(intent);
		this.mAlarmManager.cancel(pending);
	}


//AlarmReceiver

public class AlarmReceiver extends BroadcastReceiver
{
	@Override
	public void onReceive(Context context, Intent intent)
	{
		Bundle extra = intent.getExtras();
		if (extra != null)
		{
			boolean isOneTime = extra.getBoolean("one_time");
			if (isOneTime)
			{
				AlarmDataManager.getInstance().setAlarmEnable(context, false);
                                // 알람 울리기.
			}
			else
			{
				boolean[] week = extra.getBooleanArray("day_of_week");

				Calendar cal = Calendar.getInstance();

				if (!week[cal.get(Calendar.DAY_OF_WEEK)])
					return;

                                // 알람 울리기.
			}
		}
	}
}


저는 이와 같이 만들어서 사용합니다.


  Android 의 ListView에서 하나의 Item이 변경이 되면 해당 Item에 대한 dataset을 변경 후 화면에 보이는 내용을 변경해 주어야만 합니다. 그리고 나서 보통 adapter에 있는 notifyDataSetChanged()라는 메소드를 호출하여 화면에 보이는 내용을 변경합니다. 

  하지만 이 방법에는 작은 단점이 있습니다. adapter에 있는 notifyDataSetChanged()메소드를 호출하게 되면 화면에 보이는 모든 ListView의 Item에 대하여 화며을 변경하게 됩니다. 즉 화면에 ListView의 Item이 5개가 있다면 5개 전부 다 다시 그려지게 됩니다. adapter에 있는 data중 하나만 변경되었지만 adapter에서는 어떤 item의 dataset이 변경되었는지 알 수 없으니까요.

  그럼 ListView의 single item에 대해서 변경된 내용을 적용하는 방법이 없을까요? 간단한 방법으로 해결 할 수 있습니다. 변경하고자 하는 View를 ListView의 메소드인 getChildAt(int index) 를 통해서 변경하고자 하는 View를 가지고 올 수 있습니다. 

public View getChildAt (int index)

Added in API level 1

Returns the view at the specified position in the group.

Parameters
indexthe position at which to get the view from
Returns
  • the view at the specified position or null if the position does not exist within the group

변경하고자 하는 index를 알지만 현재 화면에 보이는 ListView의 item에 대해서는 index를 어떻게 확인할까요?  AdapterView의 메소드 중 현재 화면에 보여지는 ListView의 첫번째 Item이 전체 몇번째 index인지를 알려주는 메소드가 있습니다. getFirstVisiblePosition()을 통해서 알 수 있습니다.

public int getFirstVisiblePosition ()

Added in API level 1

Returns the position within the adapter's data set for the first item displayed on screen.

Returns
  • The position within the adapter's data set


그럼 이 두 메소드를 통해서 간단하게 TextView의 내용을 변경해 보도록 하겠습니다.


private void updateSingleItemView(int position)
{
    int firstVisiblePosition = mListView.getFirstVisiblePosition();
    View view = mListView.getChildAt(position - firstVisiblePosition);
    TextView textTitle = (TextView)view.findViewById(R.id.text_title);
    textTitle.setText("Title Change");
}

이와 같은 방법으로 변경하고자 하는 하나의 아이템에 대해서 내용을 변경 할 수 있습니다.

하지만 이 방법은 지금 보이는 화면에View에 대한 변경된 내용이 지속도지 않습니다. 지속되게 할려면 이전에 먼저 꼭 dataset을 먼저 변경해 주시기 바랍니다. 그리고 난 뒤에 single item에 대해서 내용을 변경해야 합니다. 그 후 ListView를 scroll한다든지에 따라서 Adapter의 View getView (int position, View convertView, ViewGroup parent) 에 의해서 화면이 다시 그려질 때 dataset이 변경되어 있지 않다면 이전의 내용으로 보이게 될 것입니다.




Android 4.0 ICS 이상 부터 음악을 듣다보면 기본 런처의 lock  화면에서 이와 같이 음악을 컨트롤 할 수 있는 기능을 확인할 수 있을 것입니다. 이 기능을 활용하기 위해서는 

RemoteControlClient(http://developer.android.com/reference/android/media/RemoteControlClient.html) 를 이용하여 사용할 수 있습니다. Added in API level 14 라는건 확인하셔야 하구요.

Developer 페이지 설명을 보아도 사용 할 수 있도록 잘 설명이 되어 있습니다. 주의사항도 적혀 있구요.

A remote control client object is associated with a media button event receiver. This event receiver must have been previously registered withregisterMediaButtonEventReceiver(ComponentName) before the RemoteControlClient can be registered throughregisterRemoteControlClient(RemoteControlClient).

이와 같은 내용도 있지만. 이외 별도로 AudioFocus를 가지고 있어야 합니다.

AudioManager의 requestAudioFocus()메소드를 통해서 현재 플레이어에 AudioFocus가 있어야지 기본 런처의 lock화면에 이와 같은 음악 컨트롤 할 수 있는 화면이 나오게 됩니다.

등록하는 부분.

private void registerRemoteClient()
	{
		if (Build.VERSION.SDK_INT >= 14)
		{
			try
			{
				if (mAudioManager == null)
				{
					mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
				}
				Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
				mRemoteComponent = new ComponentName(this, AudioPlayerRemoteEventReceiver.class.getName());
				mAudioManager.registerMediaButtonEventReceiver(mRemoteComponent);
				mediaButtonIntent.setComponent(mRemoteComponent);
				mediaButtonIntent.setComponent(mRemoteControlResponder);
				PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);

				myRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
				mAudioManager.registerRemoteControlClient(myRemoteControlClient);

				myRemoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
						| RemoteControlClient.FLAG_KEY_MEDIA_STOP
						| RemoteControlClient.FLAG_KEY_MEDIA_NEXT
						| RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
						| RemoteControlClient.FLAG_KEY_MEDIA_PLAY
						| RemoteControlClient.FLAG_KEY_MEDIA_PAUSE);
			}
			catch (Exception e)
			{
				if(CNCronosLog.isDebugLevel())
				{
					e.printStackTrace();
				}
			}
		}
	}

unregister

	private void unregisterRemoteClient()
	{
		try
		{
			if (Build.VERSION.SDK_INT >= 14)
			{
				if(mRemoteComponent != null)
				{
					mAudioManager.unregisterMediaButtonEventReceiver(mRemoteComponent);
					mRemoteComponent = null;;
				}
				if (myRemoteControlClient != null)
				{
					mAudioManager.unregisterRemoteControlClient(myRemoteControlClient);
				}
			}
		}
		catch (Exception e)
		{
			if(CNCronosLog.isDebugLevel())
			{
				e.printStackTrace();
			}
		}
	}

기본 lock 화면에다가 정보 보내는 부분.

					MetadataEditor metaEdit = myRemoteControlClient.editMetadata(true);
					
					metaEdit.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, "title");
					metaEdit.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, "artist");
					metaEdit.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, "album");
					metaEdit.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, bitmap);
					metaEdit.apply();


현재 player 상태 보내는 부분.

						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
						myRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);


이와 같이 사용하면 원하는 상태로 기본 런처의 lock 화면에 음악재생의 컨트롤을 활용할 수 있을 것입니다.


안드로이드 개발을 하다 보면 지금보다 조금 더 성능을 낼 수 있는 앱을 만들고 싶어질 때가 있습니다. 개발자 마다 코딩 스킬도 틀리고 더군다나 디바이스마다 스펙도 틀리기 때문에 같은 앱을 누가 만드느냐 어떤 디바이스에서 동작하느냐에 따라서 앱의 performance가 틀리게 나옵니다. 디바이스 스펙은 어쩔 수 없지만 개발하는 중간중간 조금의 신경을 쓴다면 성능이 좋은 앱을 만들 수 있을 거라 생각 됩니다. 안드로이드 앱을 개발하면서 경험했던 내용에 대해서 조금 정리를 해볼려고 합니다.


■ Traceview Profiling (http://developer.android.com/tools/debugging/debugging-tracing.html)

자신이 개발한 메소드 안에서 수행시간이 얼마나 걸리는지 알아보고, 수행 시간이 많이 걸렸다면 그만큼 performance에 영향을 줍니다. 그중에서 Android SDK 에 포함되어 있는 Traceview라는 것이 있습니다. 각 메소드에 대해서 프로파일링 한 결과를 그래픽으로 보여주는 좋은 툴입니다. 하지만 처음 사용하면 이게 뭐지?? 라는 생각을 가지고 있는 좀 복잡해 보이기도 합니다. 


그림과 같이 "Start Method Profiling" 버튼을 클릭 후 앱을 동작시킵니다. 그럼 동작되는 동안 수행되는 메소드에 대해서 프로파일링을 진행합니다. 그리고 나서 같은 위치에서 모양이 바뀐 "Stop Method Profiling" 버튼을 클릭 하고 나면 프로파일링이 끝난 결과를 화면에 보여 줍니다.

딱 이그림이 처음 보면... 뭐 이리 복잡해?? 라는 생각을 갖게 하는 그림 입니다. 

표의 내용은.

 - Incl Cpu Time % / Incl CPU Time : 해당 메소드 및 그것이 호출하는 자식메소드를 포함한 CPU의 수행 시간.

 - Excl Cpu Time % / Excl CPU Time : 메소드가 수행된 CPU 시간

 - Incl Real Time %/ Incl Real Time : 해당 메소드 및 그것이 호출하는 자식 메소드를 포함한 실제 시간.

 - Excl Real Time % / Excl Real Time : 메소드가 수행되는 실제 시간.

 저는 이중에서 Excl Real Time% / Excl Real Time 을 중요하게 봅니다. 해당 표의 값이 높으면 다른 Excel cpu time 이나 incl real time 도 높게 나타날 것입니다. 해당 부분을 클릭 하면 아래로 더 자세한 내용이 나옵니다. 저는 테스트하기 위해서 AsyncTask에서 String의 조합을 강제로 10000회 하도록 했습니다. 전체 프로파일링 중에 대부분 해당 부분에서 수행시간이 많이 나왔습니다.

설명은 DDMS를 이용한 방법을 설명했지만 직접 trace 파일을 생성해서 traceview로 보는 방법도 있습니다. 그건 Android 개발자 페이지에 더 자세하게 나와있으니 잠깐이라도 봐보시기 바랍니다.

■ GUI Optimization

http://developer.android.com/tools/debugging/debugging-ui.html

1. Hierarchy Viewer

Hierarchy Viewer 현재 화면에 보여지는 layout에 대하여 Tree View 형태로 보여주며 개발자가 UI 인터페이스를 디버깅 하고 분석할 수 있도록 도와 주는 도구 입니다. 각 component에 대해서 소비 시간을 분석과 다른 component와 비교도 할 수도 있고 각각의 속성도 파악 합니다. 그리고 해당 component가 어떤 화면인지에 대해서도 볼 수 있습니다.

그림과 같이 화면에 대한 분석화면을 볼 수 있고,


이와 같이 각 component에 대해서 분석한 화면도 볼 수 있습니다.맨 하단에 있는 Measure, layout, and draw performance indicators for this view에 나온 녹색, 노란색, 빨간색 원을 볼 수 있는데 녹색>노란색>빨간색 순으로 좋다고 보시면 됩니다. 그런데 해당 component에 대해서 무조건 녹색을 만들기는 어려워 보입니다. layout의 depth가 깊어지면 제일 상위에 있는 layout에 대해서는 녹색보다는 노란색과 빨간색을 더 많이 보여줍니다. 그래서 부모쪽 보다는 최 하단에 있는 자식 layout과 그에 해당하는 component에 대해서 최적화를 많이 합니다. 그러다 보면 상위로 갈 수록 빨간색과 노란색 보다는 녹색의 비율이 더 많아지더군요.


 - Lint warnings : Android resource(layout. string 등등)을 검사하여 잠재적으로 오류를 나타낼 수 있는 부분에 대하여 미리 검사를 합니다. 실행에는 전혀 문제가 없지만 경고가 잠재적인 오류를 가지고 있기 때문에 확인해 볼 필요는 있습니다.

 - layoutopt : layout의 xml 파일에 대해서 분석하여 비효율적인 부분에 대해서 알려주는 console tool.

Lint Warnings와 layoutopt에 제가 보는 것은 layout의 depth입니다. 지금 layoutopt는 android sdk tool에서 보이지 않습니다. 언제 사라진 것인지도 잘 모르겠습니다. 그래서 요즘은 Lint Warnings를 통해서 layout을 확인합니다. xml에서 layout을 만들고 lint warnings로 확인하다 보면 가끔 

이와 같이 "This RelativeLayout layout or its LinearLayout parent is useless"라는 메시지를 보이는 곳이 있습니다. RelativeLayout이나 LinearLayout은 경우에 따라 달라질 수도 있습니다. 쉽게 현재 사용중인 layout의 부모 layout은 쓸모가 없다는 내용입니다. 저는 이런게 보이면 과감하게 현재보다 바로 윗단계이 있는 layout에 대해서는 과감하게 삭제 합니다. 적어도 쓸모가 없기 때문입니다. Layout 의 Depth를 적게 하면 할수록 화면을 그리는 UI Thread의 작업이 조금이나마 줄어들것입니다. 작업이 줄어들 수록 수행 속도가 빠르다는 것도 다들 알 것이구요. 

제가 경험한 것으로는 한 화면에 너무 많은 컴포넌트들을 보여주고 싶어하는 디자이너와 기획자의 요구에 따라서 화면을 구성하다 보니 많은 layout을 사용하게 되었습니다. 단방향으로 고정된 화면에서 문제가 없었지만, 수많은 layout과 depth가 깊은 화면에 대해서 Animation을 수행할 때와 layout/layout-land 로 구성된 화면에서 screen rotaton을 하여 portrait과 landscape 화면에 대하여 서로 다른 화면을 구성할 때 문제가 되었습니다. 그래서 가급적이면 화면을 구성할 때 layout의 depth를 적게 하라고 권하고 싶습니다.


■ GC(Garbage Collection)

Android 는 디바이스마다 Heap 크기도 틀리고 하다 보니.. 메모리 관리도 중요하다고 할 수 있죠. Java의 GC(Garbage Collection)만 얘기도도 너무나 많으 내용이 될듯 싶고, 찾압면 정말 자세하게 설명해둔 자료들도 많으니 따로 찾아보셔도 될듯 합니다. 앱 개발하면서 메모리를 얼마나 많이 사용 되고 있는지 확인해볼 방법은 여러가지가 있겠지만 전 두가지를 알려드릴려고 합니다. DDMS에 있는 "Allocation Tracker"와 Heap dump 파일 분석을 통해 알아볼까 합니다.

- Allocation Tracker

DDMS에서 확인하고 싶은 앱 선택 후 Allocation Tracker에서 Start Tracking 클릭 후 GetAllocations를 누르면 현재 메모리가 할당된 Object들의 목록이 나타납니다. Allocation Size를 제일 많이 차지하고 있는 항목을 선택하고 나면 두번째 테이블에는 해당 Object의 Stack Trace 정보가 출력됩니다. 만약 해당 Allocation Size가 비정상적으로 많다면 해당 방법으로 잘못된 곳을 찾을 수도 있을것입니다.

- HPROF

힙덤프 파일을 생성해서 분석해볼 수도 있습니다. 힙덤프 생성하는 방법은 adb를 이용하거나 api를 이용. 또 DDMS를 이용해서 할 수 있습니다.

1. ADB 이용

$ adb shell

# mkdir /data/misc

# chmod 777 /data/misc (/data/misc에 쓰기권한 확인)

# ps (애플리케이션 프로세스 ID 확인)

# kill -10 <pid> (SIGUSR1 시그널 전송)

(잠시후 /data/misc 에 heap-dump-*.hprof 파일 생성됨)

$ adb pull <dump-file> <as-file> (로컬로 파일 추출)

ADB shell을 이용할려면 root 권한이 있어야 하고.. 매번 이런식으로 shell 접속하기 귀찮아서 잘 사용 안합니다. ㅡㅡ.

2. API 이요

android.os.Debug.dumpHprofData(fileName);

- fileName의 위치는 쓰기 권한이 있는 곳이어야 합니다. 외장메모리 영역이면 괜찮겠죠? 대신 WRITE_EXTERNAL_STORAGE permission 지정을 잊지 않으시면 됩니다.

3. DDMS 이용.

가장 현실적인 이용방법입니다. adb shell을 일일이 드어가기 귀찮고. 그렇다고 api를 이용하면 앱 release 시에는 해당 부분을 삭제 해 줘야 하기도 하고. 그리고 해당 heap dump 파일을 분석해주는 Eclipse MAT라는 plugin도 있습니다.


Dump HPROF file 버튼을 클릭 합니다. 저가은 경우는 Eclipse MAT플러그인을 상용하는 관계로 바로 아래와 같은 그래픽컬한 화면이 나옵니다. 바로 dump된 파일을 분석해서 그래프로 보여주는 것이죠. 

Shallow Heap과 Retained Heap을 통해서 어디서 메모리 누수가 발생하는지 확인을 할 수 있습니다.

Shallow Heap : 객체 하나당 크기.

Retained Heap : 참조가 유지하고 있는 모든 객체의 크기.

만약 상위  " Retain Heap < 하위 Retain Heap + 모든 Shallow Heap "  으로 나온다면 계산하고 있는 하위 객체에 대해서 메모리 누수가 발생하고 있다고 보면 됩니다.


이외에도 WakeLock을 어떻게 사용하느냐에 따라서 앱 전체 성능에 영향이 있을 수도 있습니다.

이 방법들 이외에도 다른 방법들도 많으 것으로 보입니다. 그런데 이런것으로 확인하는 것도 좋지만, 개발자가 개발할 당시에 조금이라도 신경써서 하면 개발한다면 좋은 성능을 낼 수 있는 앱을 만들 수 있겠죠. 개발이 완료되어 앱이 릴리즈 되었다면 개발되었던 것을 다시 돌아보면서 리펙토링 하는 시간도 갖는다면 좋을 것이구요. 하지만 현실은 그렇지 않아 보입니다. 현실은 하나 완료하기도 전에 다음 업데이트 일정들이 나오고 있으니까요.





Android에서 보통 support-v4 library를 많이 사용하죠. ViewPager와 Fragment등등 때문에...

그런데 이번에 ActionBar를 하위 버전에서 사용할 수 있도록 suppert-v7이 변경되어서 나왓습니다.

google에서 가면 갈수록 api가 상위버전에서만 사용할 수 있는 것들을 하위 버전에서 사용할 수 있도록 해주니 좋긴 좋군요. 그런데 하위버전에서 안되서 기존의 있는 sdk를 이용하여 custom하게 만들어서 사용하던 분들은 약간의 짜증?? 이 날 수도 있겠죠 ㅋㅋ 저같은 경우는 ViewPager가 안나왔을 때 ViewPager와 같은 기능을 만들어서 잘 사용하고 있더니 suppert-v4에 ViewPager를 넣어서 내놓더군요..... 욕심에 제가 만든걸 잘 사용하다가 결국엔 ViewPager로만 사용하게 되었던 ^^


자 그럼 ActionBar를 사용하기 위한 suppert-v7의 이용 방법 입니다.


전 eclipse 3.7 Indigo와 ADT 21을 이용합니다. 현재 eclipse는 4.3 kepler가 최신이고, ADT는 22입니다. 


1. Android SDK Manager를 이용하여 Android Suppert Library를 rev.18로 업데이트 한다.

2. Android Project from Existing Code

3. suppert-v4 하위에 있는 appcompat library 프로젝트 추가.

 - SDK가 설치된 하위 폴더의 extra->android->compatibility->v7->appcompat 추가.

   ex)/Volumes/Macintosh DATA/03.sdk/android-sdk-macosx/extras/android/compatibility/v7/appcompat


4. 추가된 부분 확인 - 아래 그림과 같이 support-v7-appcompat의 library프로젝트가 추가된 것을 확인할 수 있습니다.

5. Sample Project만들기.

 - 간단한 샘플 프로젝트를 만든 뒤 Properties->Android->Library에서 좀전에 추가한 support-v7-appcompat프로젝트를 추가합니다. 그리고 Project Build Target은 4.1.2 (API 16)이상으로 해야 합니다. suppoert-v7-appcompat이 4.1.2(API 16)으로 되어 있기 때문에...


6. AndroidManifest.xml의 Theme변경

android:theme="@style/AppTheme" -> android:theme="@style/Theme.AppCompat"

으로 변경합니다. 어떤 구조로 되어 있는지는 suppert-v7-appcompat의 resource인 themes.xml파일을 살펴보시면 됩니다.


7. ActionBarActivity 

import android.support.v7.app.ActionBarActivity;

public class MainActivity extends Activity -> public class MainActivity extends ActionBarActivity

으로 변경


8. menu 추가

 - ActionBarCompat용 menu리소스를 추가 합니다. 기존과 틀린점이 있다면 xmlns:action_sample="http://schemas.android.com/apk/res-auto" 의 action_sample namespace입니다. 하위 아이템을 보면 [action_sample:showAsAction="always" ]의 추가된 namespace로된 속성값이 있습니다. ActionBarCompat의 namespace로 온 속성을 사용하기 위함입니다.

- action.menu.xml 내용




android:id="@+id/menu_sample"
android:title="@string/action_sample"
android:icon="@drawable/ic_launcher"
action_sample:showAsAction="always"/>



	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.action_menu, menu);
		return true;
	}

}


9. event 발생

 - 추가된 actionbar의 아이콘을 클릭했을 때 toast message를 보여주도록 했습니다.

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {

		switch (item.getItemId()) {
		case R.id.menu_sample: {
			Toast.makeText(this, R.string.action_sample, Toast.LENGTH_SHORT)
					.show();
			break;
		}
		}

		return super.onOptionsItemSelected(item);
	}


Android 의 TextView에는 shadow 효과가 기본적으로 있습니다. shadow효과로 다양한 TextStyle을 만들 수 있는데 이번 포스트에서는 shadow효과의 다양한 value에 의해서 어떤 효과가 나오는지 확인해 볼려고 합니다.


우선 TextView에 있는 shadow 효과에 사용되는 xml attribute와 method를 확인해 보면

android:shadowColor

Place a shadow of the specified color behind the text.

Must be a color value, in the form of "#rgb", "#argb", "#rrggbb", or "#aarrggbb".

This may also be a reference to a resource (in the form "@[package:]type:name") or theme attribute (in the form "?[package:][type:]name") containing a value of this type.

This corresponds to the global attribute resource symbol shadowColor.

android:shadowDx

Horizontal offset of the shadow.

Must be a floating point value, such as "1.2".

This may also be a reference to a resource (in the form "@[package:]type:name") or theme attribute (in the form "?[package:][type:]name") containing a value of this type.

This corresponds to the global attribute resource symbol shadowDx.

android:shadowDy

Vertical offset of the shadow.

Must be a floating point value, such as "1.2".

This may also be a reference to a resource (in the form "@[package:]type:name") or theme attribute (in the form "?[package:][type:]name") containing a value of this type.

This corresponds to the global attribute resource symbol shadowDy.

android:shadowRadius

Radius of the shadow.

Must be a floating point value, such as "1.2".

This may also be a reference to a resource (in the form "@[package:]type:name") or theme attribute (in the form "?[package:][type:]name") containing a value of this type.

This corresponds to the global attribute resource symbol shadowRadius.

이와 같습니다. shadowColor는 shadow의 색상, shadowDx는 shadow의 X방향 offset, shadowDy는 shadow의 Y방향 offset, shadowRadius는 shadow의 반지름이 됩니다.

샘플코드를 확인해 보겠습니다.

<LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#ffffff"
        android:padding="10dip" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:shadowColor="#7F000000"
            android:shadowDx="3"
            android:shadowDy="3"
            android:shadowRadius="0.01"
            android:text="Shadow Style"
            android:textColor="#000000" />
</LinearLayout>

이와 같이 하면 결과는

이와 같습니다. 검은 Shodow Style 아래에 회색의 shadow가 있는 것을 확인할 수 있습니다. 그림자 위치를 더 멀리 하고 싶아면 shadowDx와 shadowDy값을 더 많이 주면 됩니다.

위의 결과에 shadowRadius값을 변경했을 때 코드는

<LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#ffffff"
        android:padding="10dip" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:shadowColor="#7F000000"
            android:shadowDx="3"
            android:shadowDy="3"
            android:shadowRadius="1.5"
            android:text="Shadow Style"
            android:textColor="#000000" />
</LinearLayout>

이와 같이 했을 경우 결과는

이렇게 됩니다. shadowRadius값에 따라서 shadow모양이 변경된 것을 볼 수 있습니다.

그럼 이번에는 shadow의 방향을 바꾸도록 하겠습니다.

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#FFFFFF"

        android:padding="10dip" >
        <TextView             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:shadowColor="#7F000000"             android:shadowDx="3"             android:shadowDy="-3"             android:shadowRadius="1.5"             android:text="Shadow Style"             android:textColor="#000000" />     </LinearLayout>

이와 같이 했을 때 결과는

이와 같습니다.그림자가 오른쪽 위 방향으로 변경되었습니다. shadowDx와 shadowDy의 값의 +- 에 따라서 값이 shadow방향이 변경 됩니다. 소위 발하는 그림자가 45` 방향이면 shadowDx는 +, shadowDy는 -을 주고, 135`방향을 경우는 둘다 +의 값을 주면 됩니다.


이번에는 shoadow를 이용한 blur 효과 비슷하게 주는 방법입니다.

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#000000"
        android:padding="10dip" >

        <TextView             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:shadowColor="#FFFFFF"             android:shadowDx="0"             android:shadowDy="0"             android:shadowRadius="3"             android:text="Shadow Style"             android:textColor="#FFFFFF" />     </LinearLayout>

이와 같이 shadowDx와 shadowDy의 값을 0.. shadowRadius값만 +로 주었을 때 결과는

이와 같습니다. 글자 주변으로 blur 효과가 나타는 것을 볼 수 있습니다.


이번에는 네온사인?? 같은 효과 입니다.

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#000000"
        android:padding="10dip" >

        <TextView             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:shadowColor="#FFFFFF"             android:shadowDx="0"             android:shadowDy="0"             android:shadowRadius="2"             android:text="Shadow Style"             android:textColor="#000000" />     </LinearLayout>
    <LinearLayout         android:layout_width="fill_parent"         android:layout_height="wrap_content"         android:background="#000000"         android:padding="10dip" >
        <TextView             android:layout_width="wrap_content"             android:layout_height="wrap_content"             android:shadowColor="#00FF00"             android:shadowDx="0"             android:shadowDy="0"             android:shadowRadius="2"             android:text="Shadow Style"             android:textColor="#000000" />     </LinearLayout>

이와 같이 했을 때 결과는


이와 같습니다. 이건 단지 배경과 글자색을 같게 하고 그림자 색상만 다르게 했을 때 입니다. 


TextView의 shadowDx, shadowDy, shadowRadius값에 따라서 TextView의 효과를 확인해보았습니다. 그런데 저는 제일 많이 쓰는 것은 단지 ShadowDx=2 shadowDy=2 shadowRadius=2 인것 같습니다. 이건 단지 개인별로 차이가 있겟지만요 ^^




 오랜만에 제 블로그에 글을 써봅기 시작합니다 ^^

 안드로이드 개발을 시작한지... 좀 되었네요 ㅎㅎ... 관심있어서 공부한 시간은 빼고 앱을 처음 만들어서 런칭했을 때가 2010년이니 시간이 참 많이도 지났네요 ㅎㅎ 제 블로그에 제가 회사다니면서 만든 앱을 적어둔 것도 있지만 안그런 것도 많아서ㅋ

 그럼 제가 개인적으로 이번에 만들어본 어플을 정리해 볼까 합니다.

우선 마켓은 

https://play.google.com/store/apps/details?id=com.magimon.quicktouch

이곳입니다.

이름은 퀵터치(QuickTouch)입니다. 앱 이름을 정하는 것도 참 힘들더군요 ㅡㅡ...

 주된 기능은 다른 앱이 실행되더라도 항상 퀵터치 뷰가 보여지고, 뷰안에서 모바일 데이터, 배터리, 메모리 등을 모니터링 합니다. 그리고 항상 빨리 실행하고 싶은 앱을 추가해서 다른 앱을 실행하더라도 그 위치에서 바로 실행할 수 있습니다. 머 이게 주된 기능이라고 보면 될듯 하네요.

 모바일 데이터를 모니터링 해주는 앱은 많지만, 이것을 확인할려면 위젯이나 안드로이드의 notification bar에서 확인해야 합니다. 다른 앱이 실행중이면 위젯은 가려져서 확인할 수 없고, notification bar에서는 화면을 내려봐야 사용된 내용을 확인할 수 있죠. 이런것도 단순히 항상.. 실시간으로 확인해 보는 것입니다. 모바일 게임을 하면서 항상 데이터를 확인할 수 있죠.

 안드로이드느 백그라운드에서 앱이 많이 실행됩니다. 여러가지 서비스도 많이 돌고, 넥서스 시리즈가 아닌 국내 제조사의 폰들은 자체적으로 또 많은 추가적인 서비스를 넣죠. 요즘 최신 디바이스들이 메모리가 2기가 가까이 되지만 메모리를 기본적으로 많이 사용하고있죠. PC를 생각해도 메모리를 많이 사용하면 PC전체가 조금 느려지기도 하고 .. 그래서 메모리 사용량을 실시간으로 모니터링해 보여주고, 메모리를 사용자 설정보다 많이 사용하게 되면 메모리를 clear하라는 경고를 알려줍니다. 

 하상 퀵터치 뷰가 맨위에 존재하다 보니 이걸 통해서 언제든지 사용자가 등록해둔 앱을 빨리 실행해 볼 수도 있습니다. 기본적으로 하나의 앱을 실행하는 도중에 다른앱을 실행할려면 백키나 홈키를 통해서 빠져 나온 뒤 실행해야 하지만, 이것은 바로바로 실행할 수 있죠. 조금의 시간이라도 절약할 수 있는 겁니다 ^^


 

 

 

 

 

 


아직 수정하고 손봐야 할 곳들이 많습니다. 추가했으면 하는 기능은 댓글로 남겨주시거나 메일로 보내주셔도 됩니다 ^^ 소중한 의견은 앱의 발전에 큰 도움이 됩니다 ㅋ



MapView 에서 현재 화면의 중앙 latitude와 longitude 값을 얻는 것은 간단 합니다.

getMapCenter() 메소드를 통해서 GeoPoint를 얻기만 하면 되니까요...
그럼 화면의 최상단과 최하단의 latitude, longitude 값은 어떻게 얻을까요?
아래와 같이 하면 됩니다.

Projection proj = mMapView.getProjection();
GeoPoint topLeft = proj.fromPixels(0, 0);

GeoPoint bottomRight = proj.fromPixels(mMapView.getWidth() - 1, mMapView.getHeight() - 1);

double topLat = topLeft.getLatitudeE6() / 1E6;
double topLon = topLeft.getLongitudeE6() / 1E6;
double bottomLat = bottomRight.getLatitudeE6() / 1E6;
double bottomLon = bottomRight.getLongitudeE6() / 1E6;



이렇게 하면 현재 mapview 화면의 상단 하단의 latitude와 longitude 값을 구할 수 있습니다.
 

+ Recent posts