반응형

 

RecyclerView #1- 구조및 기본 사용법

 

RecyclerView #1- 구조 및 기본 사용법

RecyclerView #2 - 구분선 추가, 아이템간 간격 조절

RecyclerView #3 - 컨텍스트 메뉴 처리

RecyclerView #4 - 아이템 클릭 처리

RecyclerView #5 - 아이템 선택 처리하기

RecyclerView #6 - ItemView를 클래스화 하기

RecyclerView #7 - ViewType 동적변경

 

RecyclerView는 ListView 의 향상되고 유연한 버전이라고 설명되어 있습니다.

실제로 안드로이드 디벨로퍼 사이트의 ListView에 대한 설명 초기에 

목록을 표시하는보다 현대적이고 유연하며 효율적인 방법을 사용하려면 android.support.v7.widget.RecyclerView를 사용하라고. 언급되어 있습니다.

'ListView 호환 상위 위젯으로 받아들여도 무방할 것 같습니다.

 

RecyclerView는 ListView와 달리 "Item을 표시하는 각각의 View를 효율적으로 재사용할 수 있도록 구성되어" 있습니다.

다만 RecyclerView는 표시되는 각 Item의 selection을 기본 지원하지 않기 때문에 귀찮은 처리가 일부 동반됩니다.

 

#1

 

RecyclerView 구성요소 및 동작개념

 

ViewHolder

RecyclerView는 ViewHolder를 통해 각 항목(Item)을 표시할 View를 관리하도록 하는 구조로 설계되어 있습니다. 

ViewHolder는 RecyclerView.ViewHolder 를 상속받아 구현하게 되며, ViewHolder를 통해 item을 표시할 View를 보관하고 필요에 따라 재사용 할수 있게 됩니다. 

 RecyclerView 는 화면에 표시되는 개수만큼의 ViewHolder와 여분의 ViewHolder를 기본으로 생성하여 스크롤에 대비하며, 

스크롤등의 동작으로 인해 "사라지는 아이템"을 표시하고 있는 ViewHolder를 "새로 표시되는 아이템"을 그리는데 재사용함으로써,

대용량의 데이터라도 소수의 View를 이용하여 표현할 수 있습니다.

 

아래의 이미지는 총 50개의 데이터를 RecyclerView에 삽입하고, 특정 아이템을 클릭할시 배경색을 파란색으로 변경하도록 작성한

샘플 프로그램입니다.( 아이템을 선택할 때를 제외하고는 배경색을 변경하는 코드는 없습니다.)

클릭한 첫번째 아이템뷰는 화면 스크롤에 따라 13번째 아이템을 표시하기위해 재사용되었고, 

스크롤을 계속하게되면 12번 간격으로 파란색으로 선택된 View가 재 사용됨을 볼수 있습니다.

 

 

 

 

 

아래의 이미지를 통해 좀더 확실히 살펴보면,

이 예제 프로그램에서는 한 화면에 표시되는 ViewHoler 8개 + 여분의 ViewHoler 4개가 생성되어 재활용 되고 있음을 알 수 있습니다.

 

 

 

 

Adapter

 위에서 설명한 ViewHolder 객체는  RecyclerView Adapter에 의해 생성 및 관리 되며,

Adapter는 RecyclerView.Adapter 를 상속받아 작성하게 됩니다.

Adapter는 ViewHolder를 position(위치) 기반으로 할당하고, 

Data를 ViewHoler에 반영할 수 있도록  onBindViewHolder() 콜백을 호출하게 됩니다.

다시말해 onBindViewHolder 콜백 호출을 받았을때, 위치(position) 기반의 데이터를 할당된 ViewHolder에 표시할 수 있습니다.

 

 

RecyclerView 사용 예제 구현

 

dependecy

RecyclerView를 사용하기 위해서는 build.gradle(app)에 다음과 같은 dependency 추가가 필요합니다만.

dependencies {
    ...
    implementation 'com.android.support:recyclerview-v7:28.0.0'
}

Layout 편집기 상에 RecyclerView를 Drag&Drop하면 다음과 같은 메시지 박스가 팝업되며 [OK]를 누르면 자동 추가 됩니다.

 

MainActivity Layout

가장 단순한 형태를 사용합니다.

LinearLayout 안에 RecyclerView만 포함된 형태입니다.



<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView> </LinearLayout>

 

 

Adapter & ViewHolder구현

RecyclerView.Adapter

 

위의 개념 설명에서 Adapter는 

RecyclerView.Adapter 를 상속하여 작성하며, 

필요에 따라 ViewHolder를 생성하고 

관리한다고 설명했습니다.

 

public abstract static class Adapter<VH extends RecyclerView.ViewHolder>

 

다음과 같이 3개의 메소드는 필수로 구현하여야 합니다. ( Ctrl + I )

 

메소드 이름을 통해

ViewHolder를 생성하고, 바인드하고, 아이템의 전체 개수를 알려주는 메소드가 필수 구현되어야 함을 유추해 볼수 있습니다.

 

 

RecyclerView.ViewHolder

 

RecyclerView.ViewHolder class의 경우 abstract class일 뿐만 아니라, 

대부분의 경우 원하는 레이아웃을 적용해야 하므로,

RecyclerView.ViewHolder를 상속하는 별도의 Class를 작성해야 합니다. 

 



public class StdRecyclerAdapter extends RecyclerView.Adapter<StdRecyclerAdapter.StdViewHolder> {

@NonNull
@Override
public StdViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}

@Override
public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
}

@Override
public int getItemCount() {
}


public static class StdViewHolder extends RecyclerView.ViewHolder{
public StdViewHolder(@NonNull View itemView) {
super(itemView);
}
}
}


 

 

 

이제 필요한 뼈대는 작성이 되었으니, 동작하도록 코드를 추가해 보겠습니다.

 

Adapter's : onCreateViewHoler()

리스트 내의 항목을 표시하기 위한 View를 생성하고, 해당 뷰를 관리(hold)할 ViewHolder를 생성하여 리턴합니다.

 
// Create new views (invoked by the layout manager) @NonNull @Override public .StdViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { // create a new view LayoutInflater inflate = LayoutInflater.from(mContext); View view = inflate.inflate(R.layout.list_item, parent, false); StdViewHolder vh = new StdViewHolder(view); return vh; }

 

list_item.xml layout

은 LinearLayout 안에 TextView를 하나 배치한 형태입니다.

상위 LinearLayout의 layou_height를 match_parent로 하지 않도록 주의하세요.

아이템이 하나밖에 표시되지 않는 것 처럼 보입니다. 실제로 삽질한건 안 비밀...(쿨럭..).



<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="TextView" /> </LinearLayout>

 

 

ViewHolder's : construct()

생성자의 인자로 전달된 View와 관련되 사항을, 필요에 따라 가공하여 보관합니다.

ViewHolder의 의미 그대로 View를 Hold 하는 역할을 수행합니다.

 

 

* StdViewHodlder의 생성자로 넘어온 itemView는 ViewHolder.itemView에 저장되므로
여기서 별도로 저장하지 않더라도, ViewHolder 객체를 통해 접근이 가능합니다.
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class StdViewHolder extends RecyclerView.ViewHolder{
// each data item is just a string in this case


public TextView textView;



public StdViewHolder(@NonNull View itemView) {

super(itemView);

this.textView = itemView.findViewById(R.id.textView);
}

}

 

 

Adapter's : onBindViewHolder()

인자를 통해 전달된 ViewHolder 객체에 position에 기반한 데이터를 할당(표시) 합니다.



// Replace the contents of a view (invoked by the layout manager) @Override public void onBindViewHolder(@NonNull StdViewHolder holder, int position) { // - get element from your dataset at this position // - replace the contents of the view with that element holder.textView.setText(mdata.get(position)); }

 

 

MainActivity

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init(){ RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); // use this setting to improve performance if you know that changes // in content do not change the layout size of the RecyclerView recyclerView.setHasFixedSize(true); LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); List<String> dataSet = new ArrayList<String>(); dataSet.add("C/C++"); dataSet.add("Java"); dataSet.add("Kotlin"); dataSet.add("Python"); int i = dataSet.size(); StdRecyclerAdapter mAdapter = new StdRecyclerAdapter(this, dataSet); recyclerView.setAdapter(mAdapter); } }

 

오늘은 여기까지만...

 

참고자료

https://developer.android.com/guide/topics/ui/layout/recyclerview

반응형
반응형

getInflater


LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

LayoutInflater inflater = LayoutInflater.from(context);




Color

ContextCompat.getColor(getContext(),R.color.list_bg_normal));

public void setBackgroundColor( @ColorInt int color) {

vContainer.setBackgroundColor(color);

}


isetBackgroundResource( R.color.list_bg_normal);

public void setBackgroundResource( @DrawableRes int resid) {

vContainer.setBackgroundResource(resid);

}


String

String categoryName = Any.getContext().getResources().getString(R.string.default_category_name);



ImageView to Bitmap

Bitmap bitmap = ((BitmapDrawable) mivSumnail.getDrawable()).getBitmap();

반응형
반응형

복잡해 보이지만, 알고보면 간단한 Navigation Drawer Activity의 구조


안드로이드 모바일 앱에서 많이 보이는 Navigation Drawer Activity의 '레이아웃 구조'에 대해 정리 합니다.


뜯어보면 몇가지 레이아웃을 조합해서 만들어 놓은 UI 형태인데,

처음 접할때는 전체 구조가 머리속에 잘 안들어 오더라구요.



Navigation Drawer는 Android 프로젝트 생성시에도 기본으로 제공되는 Activity 타입니다.



Navigation Drawer Activity타입으로 프로젝트를 생성하면


기본으로 네개의 레이아웃을 생성해 주는데요, 각 레이아웃에 대해 간단히 분석해 보겠습니다.


(* app_bar_main.xml에 생성되는 FloatingActionButton은 설명의 편의를 위해 삭제 했습니다.)


[activity_main.xml]


DrawerLayout안에 app_bar_main을 포함하고 NavigationView를 배치한 형태 입니다.
app_bar_main은 include 형태로 포함되어 있으며, 

<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />


nav_header_main은 NavigationView의 headerLayout 속성으로 설정되어 NavigationView가 그려질때 Header 영역에 그려지게 됩니다.
하위에 표시되는 메뉴는 activity_main의 app:menu="@menu/activity_main_drawer" 에 의해서 결정됩니다.

 

<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />


@drawer_layout ( DrawerLayout )

DrawerLayout은 윈도우의 한 쪽 혹은 양쪽 모서리에서 끌어낼 수있는 '서랍(Drawer)' 형태의 뷰와 

인터렉션을 위한 윈도우의 최상위 컨테이너 역할을 하는 레이아웃 입니다. ( ???? )

activity_main.xlm의 전체를 감싸고 있는 레이아웃이 DrawerLayout(@drawer_layout)위젯이며 

아래와 같은 코드를 통해 ActionBarDrawerToggle 인스턴스와 결합됩니다.




뿐만 아니라, Navigation View를 열거나 닫기 위해 다음과 같이 사용됩니다.




@nav_view ( Navigation View )

Navigation View는응용 프로그램의 표준 탐색(Navigation) 메뉴를 대표(?) 합니다. (메뉴 내용은 메뉴 리소스 파일로 채울 수 있습니다.)
NavigationView.OnNavigationItemSelectedListener 리스너를 설정하여, navigation menu 선택에 따른 동작을 처리 할 수 있습니다.




nav_header_main.xml

보통 홈버튼을 누르게 되면 펼쳐지는 Navigation뷰의 Header 부분을 표시하는 레이아웃 입니다.

이 레이아웃은 Navigation View의 header 영역을 표시하는 용도로 사용됩니다.


(* 포함된 ImageView나 TextView등의 위젯은 본 주제에서는 큰 의미가 없으므로 설명은 스킵합니다.)



app_bar_main.xml

Navigation View가 펼쳐지기 전에 전체 스크린으로 보게 되는 레이아웃 입니다.
여기부터는 일반적으로 구성되는 기본 레이아웃과 크게 다르지 않습니다. 
상단에 toolbar를 품은 AppBarLayout 을 배치하고, 
아래에 실제 내용을 보여줄 content_main을 include 하고 있습니다.

<include layout="@layout/content_main" /> 



content_main.xml

실제 보여주고자 하는 내용을 표현하게 될 content_main layout입니다.
ConstraintLayout이 content_frame을 감싸고 있습니다.


Navigation Drawer Layout은 일반적으로 Navigation 메뉴를 선택시 Fragment를 교체하는 형태로 사용되는데,

다름과 같은 코드로 content_frame을 교체하는 방식으로 사용된다.

case R.id.nav_tools {

FragmentTransaction ft = getFragmentManagerInstance().beginTransaction();

Fragment fragment = new ToolFragment();

ft.replace(R.id.content_frame, fragment, TOOL_FRAGMENT_TAG);
ft.commit();
break;

}


Navigation Drawer ...

영어를 그대로 읽고 쓰다 보니 생각해 본적이 없었는데

직역하면 '탐색 서랍' 인가요???



반응형

'안드로이드' 카테고리의 다른 글

RecyclerView #1- 구조 및 기본 사용법  (0) 2019.03.13
코드 모음  (0) 2019.03.08
Android resource compilation failed  (3) 2019.02.23
Activity간 Object 공유- Parcelable  (0) 2019.02.21
EditText 포커스 문제  (0) 2019.02.13
반응형

Android resource compilation failed


오픈소스를 하나 다운받이 분석해 보려 한다.

타겟은 omniNotes


소스를 다운받고, 빌드해서 실행해 보려 했으나 빌드 부터 말썽이다.


발생하는 오류는

Android resource compilation failed

[Source Path]\omniNotes\build\intermediates\incremental\mergeFossDebugResources\merged.dir\values\values.xml:896: error: <item> inner element must either be a resource reference or empty.

Source Path]\omniNotes\build\intermediates\incremental\mergeFossDebugResources\merged.dir\values\values.xml:898: error: <item> inner element must either be a resource reference or empty..

.


구글링 해보니 관련 이슈가 엄청나게 많다.


이해한 수준에서 간단히 정리하자면
Gradle Build tool이 업데이트 되면서, <item> 태그의 규칙을 엄격하게 체크하게 된것(?) 

이런식으로 기술된 것들을

<item name="child_text" type="id">childText</item>


아래와 같이 수정해 주어야 한다.

<item name="child_text" type="id"> 


즉 에러에 나와 있는 문구를 준수하면 된다.

<item> inner element must either be a resource reference or empty..


하지만, 전체 프로젝트를 search해 봐도 유사한 부분은 없다.

이번 케이스는 빌드하면서 자동생성되는 파일이기 때문에 수정이 불가하다는 얘기다


아마도 프로젝트가 참조하는 라이브러리에 관련 부분이 포함되어 있고, 
리소스를 컴파일 하면서 해당 부분이 문제가 되는 케이스로 추측된다.

혹시나 해서 최신버전이 아닌 라이브러리 들을 모두 최신으로 업데이트 해봤지만
에러가 발생하는 파일의 갯수가 줄기는 했지만 완전히 해결이 되진 않았다.

이리 저리 찾아보다가, 수정사항을 폐기하고 github에서 소스들 다시 받았더니 정상적으로 빌드가 되기 시작한다.
이건 뭐....
찝찝하지만 그상태로 소스를 보다가, 다음날 다시 해보니 또 동일한 오류가 발생한다.

어제 github에서 소스를 다시 받으니 빌드가 되었던것이 생각나서 변경사항을 확인해 보니
다음과 같이 딱 두군데가 차이가 있다.



Gradle Scripts > build.gradle(Project)의 com.android.tools.build:gradle버전을 3.3.1에서 3.1.2로 수정해 주니 정상적으로 빌드가 된다.
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath 'me.tatarka:gradle-retrolambda:3.2.5'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6"
classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1'
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
} 



오늘 프로젝트 빌드시 팝업된 업데이트를 별생각없이 진행했는데 이 영향을 받은 것으로 보인다.





물론, 문제의 소지는 여전히 가지고 있고, 근본적인 해결책이 되진 않는다만
우선은 소스 분석이 목적이니 그냥 넘어가도록 하자.



* 처음에는 이런 에러도 떴었는데, 이리 저리 하다 보니 없어졌는데, 정확한 조치가 어떤것이었는지는 정확히 모르겠음


C:\Users\xxxx\.gradle\caches\transforms-1\files-1.1\library-0.0.2.aar\88f4da1871053ebc65d966332b9adccc\res\values\values.xml:52:5-49: AAPT: error: <item> inner element must either be a resource reference or empty. 


* 대략 다음과 같은 가이드가 존재한다.

1. 위에서 설명한 <item type="id> 관련 부분 수정

2. Gradle.property에 android.enableAapt2=false 추가

3. Build 폴더 삭제


* 참고사이트

https://stackoverflow.com/questions/52503768/android-resource-compilation-failed-in-v3-2

https://stackoverflow.com/questions/52076491/android-inner-element-must-either-be-a-resource-reference-or-empty

https://blog.codejun.space/37



반응형

'안드로이드' 카테고리의 다른 글

코드 모음  (0) 2019.03.08
Navigation Drawer 의 Layout구조  (2) 2019.02.27
Activity간 Object 공유- Parcelable  (0) 2019.02.21
EditText 포커스 문제  (0) 2019.02.13
Android Studio 단축키  (0) 2018.12.21
반응형

Activity간의 Object 공유 - Parcelable

 

하나의 Activity에서 다른 Activity로 데이터를 전달해야 하는 경우에

데이터를 전달하는 Activity에서는 Intent Class의 putExtra() 메소드를 이용해서 데이터를 세팅하고,

받는 쪽에서는 getExtra() 메소드를 이용해서 받게 된다.

 

puExtra()을 이용해서 전달할수 있는 데이터 타입은 대략 다음과 같다.

 

 

ActivityA에서 Book Class의 인스턴스를 관리하고,

ActivityB에서 Book Class의 정보를 UI에 표시하는 상황을 가정해 보자.

 

Book Class는 아래와 같다.

public class Book {
    int serial;
    String isbn;
    String title;
    Bitmap image;
}

 

Book Class의 전체 프라퍼티를 ActivityA에서 ActivityB로 전달하려면 아래와 같이, 프라퍼티 별로 하나씩 put()하고 get()할 수 밖에 없다.

MainActivity

intent.putExtra("serial", book.getSerial());
intent.putExtra("isbn", book.getIsbn());
intent.putExtra("title", book.getTitle());
intent.putExtra("image", book.getImage());
BookActivity

int serial = intent.getIntExtra("serial", 0);
String isbn = intent.getStringExtra("isbn");
String title = intent.getStringExtra("title");
Bitmap image = intent.getParcelableExtra("image");

 

아래와 같이 사용하고 싶은 생각이 들것이다.

MainActivity

Book book = new Book(1, "1-1-1-1", "슬럼독 밀리어네어", bitmap);

Intent intent = new Intent(getApplicationContext(), BookActivity.class);
intent.putExtra("book", book);
BookActivity

Book book = intent.getExtra("book");

 

우선은 Intent  putExtra() 메소스 중, 아래의 두가지 함수원형을 눈여겨 보자!

putExtra( String name, Parcelable value );
putExtra( String name, Serializable value);

우리가 전달하고자 하는 Class가 Serializable 하거나 Parcelable 하게 만들 수 있다면 가능할것 같지 않은가?

 

구글링을 하다 보니 도착한 Goolgle의 가이드를 보면( 링크를 잃어 버렸다),

Serializable 보다는 Parcelable을 권장하는 듯 하니 Parcelable 을 사용하는 방법을 확인해 보기로 하자.

(Serializable의 Java의 표준 인터페이스이며, Parcelable의 안드로이드 SDK에 포함된 인터페이스 이다)

 

Parcelable은 IPC를 이용하여 데이터를 공유하며 Serialiable과는 달리 리플렉션을 사용하지 않으므로

성능면에서 유리하다고 한다.

반면 전달하는 데이터의 해석코드를 필수적으로 작성해야 하므로, 유지보수 측면에서는 상대적으로 귀찮고

번거로운 작업이 동반된다.

 

개략적으로 개념을 잡아 보자면

여러 Activity가 접근할 수 있는 영역(아마도 커널 메모리)에 

1. 전달하고자하는 데이터를 순차적으로 저장 한다.

2. 저장된 데이터를 순차적으로 읽어서 해석할 수 있는 방법을 제공한다.

* 다만 데이터를 쓰고, 읽는 방법은 Parcelable Interface가 지원하는 메소드를 통해서만 가능하다.

* 쓰여진 데이터를 해석하는데 있어 데이터가 쓰여진 순서대로 읽어야 복원이 가능하기 때문에

Write한 순서와 Read한 순서를 일치시키는 것은 반드시 지켜져야한다.

* 개념적으로 직렬화/마샬링과 유사한 개념으로 생각하면 이해가 편할것 같다.

직렬화(serialization)

마샬링(Marshalling)

 

구현 방법은 다음과 같다.

1. 공유하고자 하는 Class가 Parcelable Interface를 상속받도록 구현한다.

public class Book implements Parcelable {
    int serial;
    String isbn;
    String title;
    Bitmap image;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
    }
}

 

2. 필수 추상 메소스를 구현한다.

* writeToParcel() 메소드가 전달할 데이터를 기록하는 메소드의 구현이다.

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(serial);
        dest.writeString(isbn);
        dest.writeString(title);
        dest.writeParcelable(image, flags);
    }

 

3. CREATOR를 구현한다.

public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        public Book[] newArray(int size) {
            return new Book[size];
        }
};

 

4. 생성자를 구현한다.

* 단순히 생성자라기 보다는 Parcelable data를 해석할 수 있는 방법의 구현이다.

public Book(Parcel src) {
        readFromParcel(src);
    }

public void readFromParcel(Parcel src) {
    serial = src.readInt();
    isbn = src.readString();
    title = src.readString();
    image = src.readParcelable(Bitmap.class.getClassLoader());
}

public Book(int serial, String isbn, String title, Bitmap image) {
    this.serial = serial;
    this.title = title;
    this.isbn = isbn;
    this.image = image;
}

 

전체 코드는 다음과 같다.

Book.java

( getter/setter의 구현은 필수 적이지 않다 )

package com.tistory.thepassion.pacelable;

import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    int serial;
    String isbn;
    String title;
    Bitmap image;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(serial);
        dest.writeString(isbn);
        dest.writeString(title);
        dest.writeParcelable(image, flags);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    public Book(Parcel src) {
        readFromParcel(src);
    }

    public void readFromParcel(Parcel src) {
        serial = src.readInt();
        isbn = src.readString();
        title = src.readString();
        image = src.readParcelable(Bitmap.class.getClassLoader());
        // image = Bitmap.CREATOR.createFromParcel(src);
    }

    public Book(int serial, String isbn, String title, Bitmap image) {
        this.serial = serial;
        this.title = title;
        this.isbn = isbn;
        this.image = image;
    }

    public int getSerial() {
        return serial;
    }

    public void setSerial(int serial) {
        this.serial = serial;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public Bitmap getImage() {
        return image;
    }

    public void setImage(Bitmap image) {
        this.image = image;
    }
}

 

MainActivity.java

package com.tistory.thepassion.pacelable;

import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    Book book;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Resources res = getResources(); Drawable d = res.getDrawable(R.drawable.book);
        Bitmap bitmap = ((BitmapDrawable) d).getBitmap();

        book = new Book(1, "1-1-1-1", "슬럼독 밀리어네어", bitmap);

        Intent intent = new Intent(getApplicationContext(), BookActivity.class);
        intent.putExtra("book", book);

        startActivity(intent);
    }
}

 

ActivityB.java

package com.tistory.thepassion.pacelable;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;
import android.widget.TextView;

public class BookActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book);

        Intent intent = getIntent();

        Book book = intent.getParcelableExtra("book");
        ((TextView) findViewById(R.id.txtSerial)).setText(String.valueOf(book.getSerial()));
        ((TextView) findViewById(R.id.txtIsbn)).setText(book.getIsbn());
        ((TextView) findViewById(R.id.txtTitle)).setText(book.getTitle());
        ((ImageView) findViewById(R.id.imageView)).setImageBitmap(book.getImage());
    }
}

 

 

지금까지 읽어 내려오면서 궁금한 점이 있었는지 모르겠다.

Parcelable을 구현하기 전의 첫번째 예제에서도 Bitmap 객체를 넘겼었고,

마지막의 전체 구현 코드에서도 Bitmap 객체를 전달하고 있다.

 

예상한것처럼 Bitmap Class가 Parcelable을 implement하고 있기 때문이다.

public final class Bitmap implements Parcelable {
    private static final String TAG = "Bitmap";

    ....
    ....
}

 

Parcelable 을 통해 Object를 공유하고자 할때, 프라퍼티로 포함하고 있는 Class 또한

Parcelable 혹은 Serializable로 구현되어야 한다.

(binary data를 뽑아서 저장하고 복원할 수 있도록 한다면 불가능 하다고 할수는 없으니, 꼭 그런것은 아니다.)

 

사실 Bitmap을 포함한 Class를 다른 Activity로 넘기고 싶어 내용을 확인하고 정리하는 것이기는 한데...

Parcelable을 통해 전달할 수 있는 데이터의 사이즈에는 제한이 있다.

1MB 이상의 데이터를 전달하고자 할때 다음과 같은 에러가 발생할 수 있다.

Caused by: android.os.TransactionTooLargeException: data parcel size 1204788 bytes

 

1MB 제한은 구현한 Parcelable객체 하나당이 아닌 Activity가 속한 프로세스 전역에 걸친 한계 용량으로,

Parcelable을 통해 큰 사이즈의 데이터를 전달하는것은 적합하지 않다.

대략 50kb 미만의 데이터 사이즈를 권장하고 있다.

https://developer.android.com/guide/components/activities/parcelables-and-bundles

 

다른 측면에서 생각해보면

android.provider.MediaStore.ACTION_IMAGE_CAPTURE 를 이용해 촬영한 이미지를 얻을때,

onActivityResult 를 통해 섬네일이미지는 획득이 가능하지만, 

풀이미지를 얻기 위해서는 별도의 Uri를 통해야하는 이유를 이해할 수 있다. 

 

결국 bitmap을 전달해야 할 경우, 

Bitmap 객체가 아닌 파일 경로 혹은 대상 bitmap파일을 불러올수 있는 key를 전달하는 방식으로 우회해야 할것같다.

반응형

'안드로이드' 카테고리의 다른 글

Navigation Drawer 의 Layout구조  (2) 2019.02.27
Android resource compilation failed  (3) 2019.02.23
EditText 포커스 문제  (0) 2019.02.13
Android Studio 단축키  (0) 2018.12.21
Android Studio 화면구성  (0) 2018.05.13
반응형

Activity 생성 시, EditText에 포커스가 가는 문제




의도치 않은 동작

Toolbar메뉴를 누르면

EditText 위젯이 포함된 Activity를 하나 생성해서 띄우도록 구현했는데

Activity가 생성 -> EditText에 포커스 -> 키패드 팝업

의 순서로 의도치 않은 동작이 수행되는 군요.



그렇다고 입력을 받아야 하는 EditTxt가 포커스를 갖지 못하게 할수도 없구요.

Activity 생성시 다른 위젯에 포커스를 강제로 가게 하는 것도 다른 문제가 있습니다.


어쩌면 꼼수

다음과 같이 처리하면 이러한 문제는 해결(?) 됩니다.


결국은 다른 위젯에 포커스를 가게 하는 방식입니다만,

화면상에 보이는 다른 위젯에 포커스를 주는게 아니라, 전체 화면을 감싸는 LinearLayout에 다음 속성을 통해 포커스를 주면 된다는 거죠


테스트를 해보니 android:focusableInTouchMode="true" 설정만으로도 현상이 사라지기는 하는데...

LinearLayout의 focusable 기본값이 true라는 얘긴가?

안전하게 둘다 쓰는것으로 하겠습니다.



*Update - 2019.04

- 테스트 해보지 않음

<activity android:windowSoftInputMode="stateHidden ">

stateHidden : activity 실행시 키보드가 자동으로 올라오는 것을 방지


default: adjustUnspecified | stateUnspecified

adjustUnspecified  : system이 적절한 키보드 상태를 설정, 혹은 테마에 따라 설정

stateUnspecified : system이 알아서 상황에 맞게 설정

반응형

'안드로이드' 카테고리의 다른 글

Android resource compilation failed  (3) 2019.02.23
Activity간 Object 공유- Parcelable  (0) 2019.02.21
Android Studio 단축키  (0) 2018.12.21
Android Studio 화면구성  (0) 2018.05.13
자주쓰는 코드  (0) 2018.05.13

+ Recent posts