SlidingIconTabLayout 탭에 아이콘 넣기

구글에서는 현재 많은 샘플 코드들을 개발자가 전반적인 프로젝트 구조나 리소스, 코드 파일들을 볼 수 있도록 제공해주고 있다. 그 중에서 Material Design이 안드로이드 5.0에서 소개되면서 UI관련 소스들을 많이 추가되었고 많은 프로젝트에서 SlidingTabLayoutSlidingTabStrip를 사용한다.

프로젝트에서 ViewPager를 이용하게 되면 페이지의 이름을 보여주기 위한 레이아웃이 필요하게 되는데, 만약 탭의 이름을 텍스트로 보여주고 싶다면 구글에서 제공해주는 위의 두 파일로 충분히 구현할 수 있다. 하지만 텍스트를 사용할 경우, 다국어 지원을 하게 되면 여러가지 이슈가 발생하게 되는데 예를 들면, 한국어를 영어로 바꿀 경우에는 대부분 글자수가 많아진다. 이를 해결하기 위해서 흔히 탭의 이름을 아이콘으로 대체하고 ActionBar의 타이틀에 페이지의 이름을 노출하고 있다. 텍스트와 아이콘을 모두 지원하기 위해서 SlidingTabLayout에 아이콘도 추가할 수 있도록 간단한 인터페이스를 추가해서 오픈소스 SlidingIconTabLayout로 만들었다.

SlidingIconTabLayout

Download

repositories {
  jcenter()
}

dependencies {
  compile 'com.github.kimkevin:slidingicontablayout:1.0.0'
}

SlidingTabLayout.TabIconProvider 추가하기

SlidingIconTabLayout.TabIconProvider는 안드로이드 화면상에 노출되는 다수의 Fragment를 관리하기 위한 어답터이다. Fragment에 필요한 데이터나 리소스를 위치(Position)에 맞게 그려주기 위해서 FragmentPagerAdapter를 상속 받아야 하기 때문에 이때 아이콘을 텍스트 대신에 보여주기 위한 인터페이스가 필요하다.

public interface TabIconProvider {
    int getPageIconResId(int position);
}

SlidingTabLayout 수정

TabIconProvider를 상속받고 있다면 텍스트 대신 아이콘을 사용할 수 있다.

if (TextView.class.isInstance(tabTitleView)) {
    ((TextView) tabTitleView).setText(adapter.getPageTitle(i));
} else if (ImageView.class.isInstance(tabTitleView) && adapter instanceof TabIconProvider {
    TabIconProvider mTabIconProvider = (TabIconProvider) adapter;
    tabTitleView.setBackgroundResource(mTabIconProvider.getPageIconResId(i));
}

사용할 FragmentTabIconProvider를 추가해서 필요한 아이콘을 추가해주면 해당 Tab의 아이콘으로 노출된다.

[Andorid] Gradle Test Case 개발시 겪은 에러

이전 글 처럼 우연히 TDD에 대해서 알게되었고, 그에 관해 공부해보고 싶은 마음에 시작했다. TDD란 의미는 테스트를 기반으로 한 개발방법론이기 때문에 현재 내가 진행중인 프로젝트에 적목해 보기가 쉽지 않았다. 그로 인해 기본 프로젝트에 맞는 Test Case 개발을 진행해 보면서 겪은 문제점들에 대해서 적으려고 한다.

ActionBarActivity 사용시 겪은 에러

@Test
public void createAdapter() throws Exception{
    MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get();
    assertThat(activity, notNullValue());

MainActivity를 생성하는 과정에서 NullPointerException 에러가 발생한다. 그 이유는 MainActivity가 ActionBarActivity를 상속을 받고 있기 때문이었고 Activity를 상속 받게 되면 에러는 발생하지 않았다. 이를 위한 해결책으로 activity를 생성하고 사용하는 함수들 위에 @Test와 마찬가지로 @Config(reportSdk = 10)을 다음과 같이 선언해 주어야 한다.

@Test      @Config(reportSdk = 10)
public void createAdapter() throws Exception{
        MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get();
        assertThat(activity, notNullValue());

이유를 명확히 하기 위해서 찾아보았지만 자세한 이유에 대해 찾기가 어려웠다. 이후에 자세히 알게 된다면 다시 적을 예정.

Andorid Studio, Gradle 테스트 셋팅하기

4주 전쯤 서울에서 Vingle과 함께 하는 ‘가을밤의 코딩이야기’에 다녀왔습니다. 행사의 주제는 현재 Vingle에서는 어떻게 프로젝트를 진행하고 있는지에 대해 알려주는 자리였습니다.

TDD & Agile in Vingle

50명 정도를 초대하는 자리였지만 200명 이상이 지원했다고 합니다. 행사 이후에 실제로 어떻게 개발하는지 웹, 안드로이드, 아이폰으로 세션을 나누어 Vingle 개발자들분들이 직접 TDD로 개발하는 방법을 알려주는 자리를 가졌습니다. 그래서 이번에 배워온 것들을 기반으로 Android Studio 설치도 해보고 TDD 개발을 위한 셋팅을 일주일 가량 걸려서 해보았습니다.

Android Studio 에서는 기본으로 프로젝트를 생성하거나 기존의 프로젝트를 가져올 때 Gradle로 가져옵니다. 전 기존의 프로젝트를 가져오는 것을 목표로 셋팅을 했구요. 그래도 기본 부터 알아야 셋팅을 할 수 있을 거라 생각해서 Robolectric, Gradle 설정 부터 시작했습니다.

Gradle은 기본적으로 build.gradle 이라는 파일에서 라이브러리 설정이나 프로젝트 관계 설정과 같은 전체적인 프로젝트 설정을 담당하고 있습니다. 사실 Maven으로 설정을 시작해보려다가 요즘엔 Maven에서 Gradle로 넘어오는 추세라고 해서 그냥 일일이 찾아가며 설정을 했구요. 시작해보도록 하겠습니다.

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:0.6.+'
        // 추가할 부분
        classpath 'com.github.jcandksolutions.gradle:android-unit-test:1.0.+'

    }
}
apply plugin: 'android'

repositories {
    mavenCentral()
}

android {
    compileSdkVersion 17
    buildToolsVersion "17.0.0"

    defaultConfig {
        minSdkVersion 10
        targetSdkVersion 17
        // 추가할 부분
        packageName "com.kevin.tddtest"
    }
}

apply plugin: 'android-unit-test'

dependencies {
    repositories {
        mavenCentral()
    }

    compile 'com.android.support:support-v4:18.0.0'
    compile 'com.android.support:appcompat-v7:+'

    // 추가할 부분
    // test 관련
    testCompile 'junit:junit:4.10'
    testCompile 'org.robolectric:robolectric:2.1.+'
    testCompile 'com.squareup:fest-android:1.0.+'
}

그리고 여기에서 Robolectric 을 왜 사용하는가 하면 Android에서 기본적으로 제공해주는 UnitTest Framework는 실행시 시간이 오래 걸리는 단점이 있기 때문에 이를 보완한 Robolectric을 사용합니다. packageName 또한 잊지 말고 프로젝트 생성할 때 넣어준 프로젝트 패키지 이름을 적어주면 됩니다. 또한 build.gradle 파일은 컴파일 할 때 순서를 의미하기 때문에 정확하게 입력해야 오류를 최소화 시킬 수 있어요. 특히 apply plugin: ‘android-unit-test’를 꼭 추가하셔야 합니다.

이렇게 셋팅을 하고 나면 설정한 라이브러리를 가져오기 위해서 상단 메뉴에 위치한 Sync 버튼을 눌러서 작성한 스크립트가 적용될 수 있도록 합니다. 그러면 왼쪽 프로젝트 하단에 있는 External Libraries에 많은 라이브러리들이 자동으로 추가가 될거에요.

그리고 테스트 패키지를 만들기 위해서 다음과 같은 구조가 되도록 패키지를 만들어야 합니다.

Project Root

+  My Application

  +– src

  |   +– main

  |   |   +– java

  |   |   +– res  

  |   |   \– AndroidManifest.xml

  |   ㅏ— test    

  |   |   +— java      <-------------- Mark Directory As 에서 Test Source Root로 바꿔줍니다

  +– build.gradle

|– build.gradle

ㄴ–settings.gradle

이해가 되지 않으신다면 main가 같은 폴더 안에 test 폴더를 만드시구요. test 폴더 안에 다시 java폴더를 만드시면 됩니다. My Application에 있는 build.gradle은 비워두시면 됩니다. 이렇게 기본 설정을 마쳤는데 아직 많은 내용이 빠진 것 같아서 여러번의 수정이 필요할 것 같습니다. 저도 LittleInnov 님이 올려 놓으신 블로그를 참고해서 설정을 했거든요. 많은 도움 주셔서 감사합니다.

LittleInnov 님 블로드 주소 : http://valley.egloos.com/viewer/?url=http://deepseadk.egloos.com/1795460

JUnit Test Code

import org.junit.Before;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

/**
 * Created by kevin on 2013. 11. 8..
 */

// jUnit Test
public class RobolTest {
    int a;
    int b;

    @Before
    public void setUp() {

    }

    @Test
    public void testRobolTest2() {
        assertThat(1, is(1));
    }

    @Test
    public void testRobolTest1() {
         assertThat(1, is(1));
    }
}

Robolectric Test Code

package com.kevin.tddtest;

import org.junit.runners.model.InitializationError;
import org.robolectric.AndroidManifest;
import org.robolectric.AndroidManifestExt;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.res.Fs;

/**
 * Created by kevin on 2013. 11. 8..
 */
public class RobolectricGradleTestRunner extends RobolectricTestRunner {
    public RobolectricGradleTestRunner(final Class<!--?--> testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected AndroidManifest getAppManifest(final Config config) {
        final String manifestProperty = System.getProperty("android.manifest");
        if (config.manifest().equals(Config.DEFAULT) && manifestProperty != null) {
            final String resProperty        = System.getProperty("android.resources");
            final String assetsProperty     = System.getProperty("android.assets");
            final String packageProperty    = System.getProperty("android.package");

            final AndroidManifestExt a  = new AndroidManifestExt(Fs.fileFromPath(manifestProperty), Fs.fileFromPath(resProperty), Fs.fileFromPath(assetsProperty));
            a.setPackageName(packageProperty);
            return a;
        }
        return super.getAppManifest(config);
    }
}

org.robolectric 패키지 생성 후 AndroidManifestExt 생성

package org.robolectric;

import org.robolectric.res.FsFile;

/**
 * Created by kevin on 2013. 11. 8..
 */

public class AndroidManifestExt extends AndroidManifest {
    private static final String R = ".R";
    private String mPackageName;
    private boolean isPackageSet;

    public AndroidManifestExt(final FsFile androidManifestFile, final FsFile resDirectory, final FsFile assetsDirectory) {
        super(androidManifestFile, resDirectory, assetsDirectory);
    }

    @Override
    public String getRClassName() throws Exception {
        if (isPackageSet) {
            parseAndroidManifest();
            return mPackageName + R;
        }
        return super.getRClassName();
    }

    @Override
    public String getPackageName() {
        if (isPackageSet) {
            parseAndroidManifest();
            return mPackageName;
        } else {
            return super.getPackageName();
        }
    }

    public void setPackageName(final String packageName) {
        mPackageName = packageName;
        isPackageSet = packageName != null;
    }
}

[Android] JNI – java.lang.UnsatisfiedLinkError

 java.lang.UnsatisfiedLinkError



 오랫만에 JNI를 사용하던 SA3D 도중에 [Android] 컴파일을 cheap nfl jerseys 하고 난 후 에러가 발생했습니다. 라이브러리 호출을 담당하는 System.loadLibrary 메소드 호출 시 발생하는 에러네요. 이때 이 에러는 라이브러리를 찾지 못할 때 Türk 발생한다. wholesale mlb jerseys  그래서 먼저 경로 확인을 [Android/Error] 했더니 경로는 이상이 Ima 없었고 다름 아닌 cheap jerseys from China 함수의 이름이었다.

 그러나! 함수 이름을 수정한 후에 cheap jerseys 다시 컴파일을 하고 ring 실행을 했더니 다시 에러가 발생했다. 이 문제는 이렇게 해결! JNI를 컴파일한 후 생성되는 폴더(obj)를 삭제하고 다시 컴파일을 하면 된다.

[Android/Error] Unable to instantiate activity ComponentInfo

 현재 네이버 지도 API 라이브러리를 is 이용해서 지도를 액티비티에 띄워보려고 하는데 이런 에러가 발생하네요. 외부 라이브러리 설정도 했지만 에러를 잡을 수 없어서 cheap NBA jerseys 검색을 해봤더니 ADT가 업데이트 되면서 wholesale NBA jerseys 발생한 에러라고 합니다.


LogCat 오류 메시지

이 문제를 해결하기 위해서는 외부 & 라이브러리 설정은 한 wholesale jerseys 후에 

<

p style=”text-align: left;clear: none;float: none”>

프로젝트의 PropertiesJava Build Path -> Order and Export

모든 About? 파일을 체크해주시면 CSS?? 됩니다.

[Android] CSS단위 em/px/pt/%

CSS단위로써 우리가 흔하게 접하는 em, px, pt, %의 단위에 대해 알아보도록 하겠습니다.

글자(폰트)의 크기나, width, height의 크기를 지정할 경우 사용되며 px를 대부분 사용하긴 하지만

글자 크기를 이용할 땐 pt를 사용하기도 합니다.

기본적으로 글자크기를 보통으로 정하셨다면 1em = 12pt = 16px 와 같습니다.

계산기 사용을 원하시면 위의 주소에서 값을 입력하신 다음 원하시는 결과를 얻으실 수 있습니다.

Points

Pixels

Ems

Percent

6pt

8px

0.5em

50%

7pt

9px

0.55em

55%

7.5pt

10px

0.625em

62.5%

8pt

11px

0.7em

70%

9pt

12px

0.75em

75%

10pt

13px

0.8em

80%

10.5pt

14px

0.875em

87.5%

11pt

15px

0.95em

95%

12pt

16px

1em

100%

13pt

17px

1.05em

105%

13.5pt

18px

1.125em

112.5%

14pt

19px

1.2em

120%

14.5pt

20px

1.25em

125%

15pt

21px

1.3em

130%

16pt

22px

1.4em

140%

17pt

23px

1.45em

145%

18pt

24px

1.5em

150%

20pt

26px

1.6em

160%

22pt

29px

1.8em

180%

24pt

32px

2em

200%

26pt

35px

2.2em

220%

27pt

36px

2.25em

225%

28pt

37px

2.3em

230%

29pt

38px

2.35em

235%

30pt

40px

2.45em

245%

32pt

42px

2.55em

255%

34pt

45px

2.75em

275%

36pt

48px

3em

300%