UI 작업을 하다보면 애니메이션 효과는 피할래야 피할수 없다.
하지만 처음 디자인팀으로부터 전달 받는 경우 여러 효과가 있을 때 각 효과에 대해 정확한 데이터를 받는 경우는 드물다.
그렇기 때문에 우선 작업하고 디자인QA 를 거치는 경우가 많다.
하지만 이 lottie 라이브러리를 사용하게 되면 개발자와 디자인 작업자 모두 행복해 질 수 있다.
안드로이드 뿐만아니라 IOS , React Native , Web , Window 다 지원하기 때문에 Json파일만 있으면 모두 동일한 애니메이션을 만들 수 있다.

디자인팀에 해당 애니메이션을 json 파일로 보내달라고 요청하면 받을 수 있는데 해당 파일만 있으면 Ok!

사용법은 쉽기 때문에 업로드 하지 않는다.
하지만 사용하면서 RecyclerView 에서 사용하는 경우 2가지 이슈를 발견 했다.

첫번째 : 나는 한번만 실행 하고 싶은데 스크롤 시 계속 애니메이션 실행 되는 이슈가 있었다.
라이브러리 내부 코드를 보니 애니메이션이 다 끝나기 전에 해당 뷰가 Detach 되는 경우 Attach 될 때 강제로 animation을 실행 시키고 있었다.
그래서 다음과 같이 Detach 될때 애니메이션을 강제로 취소시켰다.

@Override
public void onViewDetachedFromWindow(@NonNull ViewHolderGeneric holder) {
    super.onViewDetachedFromWindow(holder);
    if (holder instanceof LottieViewHolder) {
        ((LottieViewHolder) holder).cancelLottieAnimation(); //아래에 LottieViewHolder method 
    }
}

//method in LottieViewHolder
public void cancelLottieAnimation() {
    if (lottieAnimation.isAnimating()) {
        lottieAnimation.cancelAnimation();
        lottieAnimation.setProgress(1f);
    }
}

두번째 : 핸들러 사용 하지 않으면 playAnimation 정상 작동 하지 않는 이슈.
아래와 같이 처리해주었다.

new Handler().post(() -> lottieAnimation.playAnimation());

이제는 편하게 애니메이션 적용해보자~!ㅎㅎ

RxKotlin을 사용하지 않고 처리하려면 까다로운 처리인데…
RxKotlin을 사용하면 간단하게 처리 할 수 있다.

class MainActivity : AppCompatActivity() {

    var count = 0
    val compositeDisposable: CompositeDisposable = CompositeDisposable()
    var publishSubject: PublishSubject<String> = PublishSubject.create()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {
            textView.text = (++count).toString()
            publishSubject.onNext(count.toString())
            Log.d("kyh", "count : $count")
        }

        compositeDisposable.add(publishSubject
            .debounce(500, TimeUnit.MILLISECONDS)
            .observeOn(AndroidSchedulers.mainThread())
            .doOnError {
                Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
            }
            .subscribe { string ->
                Toast.makeText(this, "publishSubject 사용~! api에 보낼 카운트 : $string", Toast.LENGTH_LONG).show()
            })

        compositeDisposable.add(RxTextView.textChanges(textView)
            .debounce(300, TimeUnit.MILLISECONDS)
            .map { charSequence -> charSequence.toString() }
            .filter {
                !it.equals("Hello World!")
            }
            .observeOn(AndroidSchedulers.mainThread())
            .doOnError {
                Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
            }
            .subscribe { string ->
                Toast.makeText(this, "RxBinding 사용~! api에 보낼 카운트 : $string", Toast.LENGTH_LONG).show()
            }
        )
    }

    override fun onDestroy() {
        super.onDestroy()
        compositeDisposable.dispose()
    }
}

debounce 를 사용하면 편하게 처리할 수 있다. 위의 소스는 PublishSubject 와 RxBidning Library의 RxTextView 를 사용한 예제 소스

둘중 마음에 드는 걸로 사용하면 됨

rx 문서에서는 debounce 를
only emit an item from an Observable if a particular timespan has passed without it emitting another item
라고 소개 한다.

아이템이 들어오다가 지정한 시간동안 아이템이 들어오지 않는 경우 마지막 아이템을 subscribe 를 통해 값을 보내준다. rx 는 마블을 보고 이해 하는게 제일 좋은 것 같다. 직관적으로 이해가 잘됨.

출처 http://reactivex.io/documentation/operators/debounce.html

키보드 내려갈 때 포커스 클리어 처리 하기

키보드 보여주기 및 포커스 , 숨기기 및 포커스 클리어 function


 public static void showKeyboard(EditText editText) {
        if (editText == null) {
            return;
        }
        editText.requestFocus();
        mInputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_FORCED | InputMethodManager.HIDE_IMPLICIT_ONLY);
 }

 public static void clearFocus(Activity activity) {
        View v = activity.getCurrentFocus();
        if (v == null) {
            return;
        }
        InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
        v.clearFocus();
        activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
 }

키보드 내려갈 때 감지 해서 특정한 처리가 필요 했다.
editText에서 지원 되는 키보드 감지 리스너는 보이지 않아서 따로 구현이 필요 했다.

  int lastHeightDiff = 0;
    boolean isOpenKeyboard = false;
    private final ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        synchronized public void onGlobalLayout() {
            View activityRootView = mActivity.getWindow().getDecorView().findViewById(android.R.id.content);
            int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
            if (lastHeightDiff == 0) {
                lastHeightDiff = heightDiff;
            }
            if (heightDiff > lastHeightDiff) { //keyboard show
                isOpenKeyboard = true;
            } else {//keyboard hide
                if (isOpenKeyboard) {
                    clearFocus();
                    isOpenKeyboard = false;
                }
            }
        }
    };

해당 리스너는 onCreate 시 등록 onDestroy 해제 시켜줘야 한다.
한번 불리는게 아니라 여러번 불리기 때문에 isOpenKeyboard 를 사용해서 clearFocus 처리를 해주었다.

EditText 금액 콤마 표시 Typing 할 때 느려지는 현상

EditText 금액 적을 때 3자리 마다 , 표시를 해야하는 상황이 생겼다.
TextWatcher 에서 처리 하였고 정상 작동은 했지만 버벅거리는 모습이 보였다.
해당 이슈를 찾기 위해 소요시간이 걸릴만한곳에 로그를 찍으면 소요시간을 측정 했다.
EditText.setText() 부분에서 소요시간이 조금 길어서 TextWatcher 에서 주는 Editable s 을 s.clear 와 s.append 를 통해 추가 해 주었고

android:inputType="number|textNoSuggestions"

inputType 을 number 에서 위와 같이 변경 해 준뒤 해당 이슈를 수정 할 수 있었고 변경 시 빠르게 변경할 수 있었다.

build.gradle 에서 resConfig

빌드 시간을 빠르게 하기 위해 Build.Gradle 에서 resConfig 셋팅 하는 경우가 있다.

dev {
   resConfigs "ko", "xxxhdpi"
}

주의 해야 할 점은 ko xxxhdpi 리소스를 제외한 모든 resource를 지우고 빌드 하기 때문에 실행 하고 나서 앱 내에서 언어를 변경 하게 되면 ko 리소스 빼고는 이미 지워진 상태이기 때문에 locale 셋팅을 바꾸더라도 다른언어로 변경 되지 않는다.

이 부분을 인지하고 있지 않은 상태에서 다국어 지원을 할 경우 원인을 찾기 쉽지 않다.