본문 바로가기

Android/Function

[Java/Kotlin] Dynamic Recycler View 구현 & Local Database에 데이터 저장(Room) & LiveData 사용하기.

728x90
반응형
SMALL

 

안녕하세요. 이번 포스트는 아주 유용한 예제일것 같은데요^^

동적으로 삭제, 추가 가능한 리사이클러 뷰를 구현하면서 리사이클러 뷰의 데이터들이 앱 내 저장 될 수 있는 기능은 거의 필수적이겠죠.

이번 포스팅에서는

  • 동적으로 삭제/추가/편집 가능한 리사이클러 뷰 구현
  • Room을 사용하여 로컬 데이터 베이스에 데이터 저장
  • LiveData를 사용하여 DB 데이터 변화 감지하여 UI업데이트

를 한큐에! 해결하는 아주 유용한 예제가 되겠습니다.

동적으로 삭제/추가/편집하는 리사이클러 뷰에 Room과 LiveData를 결합하게 되겠는데요.

Room은 SQLite의 추상 레이어를 제공하여 SQLite의 기능을 모두 사용할 수 있고, DB로의 접근을 편하게 도와주는 라이브러리 입니다.

SQLite를 직접쓰지않고 Room을 사용해야 하는 이유는 찾아보니 아주 많네요. (무엇보다, 간편하다는것!)

또한 LiveData로 실시간으로 데이터 변화를 감지해 UI(리사이클러 뷰)를 업데이트 해야 하는데.

찾아보니 MVVM 패턴을 이용하는 수많은 예제들.. 또한 코드는 온니 코틀린뿐 ^^;;; 

MVVM패턴이 뭐야? 하거나 코틀린을 아직 안배운 저같은 새싹 개발자들에게 도움이 될것 같아요. (그런데 코틀린은 이제 필수라고 하네요^^;; 어서 공부해야겠어요.)

* 코드는 Kotlin도 추가하였습니다.

MVVM패턴은 ViewModel을 이용하여 Data가 변하는것을 관찰하여 UI를 업데이트 할 수있는 패턴인데, 

관리가 유용하여 장점이 많은 패턴인듯해요. 하지만 처음 보기에는 코드가 복잡하여 적용하기 쉽지 않으므로,

레파지토리나 그런 패턴 구현 없이 가볍게 구현하였으니 감안해 보아주세요^^.

*MVVM 패턴으로 작성한 코드 는 밑의 글들을 참조해 주세요.

코틀린 코드의 경우 밑의 포스트의 코드가 최신입니다.

[Android/이론] - MVVM 시작하기(1) ― LiveData, Room을 MVVM패턴으로 사용해보자

[Android/이론] - MVVM 시작하기(2) ― Room Entity, Dao, Database 만들기

[Android/이론] - MVVM 시작하기(3) ― ViewModel, 데이터 바인딩(Data Binding)

[Android/이론] - MVVM 시작하기(4) ― Repository, Model-View 연동

 

코드 미리보기

 

 

 

종속 항목 추가


RecyclerView, Room, LiveData(Lifecycle) 종속성을 추가해줍니다.

  • androidx.RecyclerView 최신버전 확인
  • Room 최신버전 확인
  • Livecycle 최신버전 확인

 

build.gradle(Module: app)

Java

dependencies {
    //Recycler View
    implementation "androidx.recyclerview:recyclerview:1.1.0"
    // Room components
    implementation "androidx.room:room-runtime:2.2.5"
    annotationProcessor "androidx.room:room-compiler:2.2.5"
    androidTestImplementation "androidx.room:room-testing:2.2.5"
    // Lifecycle components
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    //noinspection LifecycleAnnotationProcessorWithJava8
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.2.0"
    // UI
    implementation "com.google.android.material:material:1.1.0"
    // Testng
    androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
}

 

또한 람다식을 사용하기 위해 추가하였습니다.

android {
  
    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }

}

 

Kotlin

apply plugin: 'kotlin-kapt'
...
dependencies {
    //Recycler View
    implementation "androidx.recyclerview:recyclerview:1.1.0"
    // Room components
    def roomVersion = '2.2.6'
    implementation "androidx.room:room-runtime:$roomVersion"
    kapt "androidx.room:room-compiler:$roomVersion"
    implementation "androidx.room:room-ktx:$roomVersion"
    androidTestImplementation "androidx.room:room-testing:$roomVersion"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
    // Annotation processor
    kapt "androidx.lifecycle:lifecycle-compiler:2.2.0"
    // UI
    implementation "com.google.android.material:material:1.1.0"
    // Testng
    androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
}

Layout


 

 

activity_main.xml


<androidx.recyclerview.widget.RecyclerView
     android:id="@+id/rv_record"
     android:layout_width="match_parent"
     android:layout_height="wrap_content" />

Recylcer View 레이아웃 코드를 위처럼 추가해줍니다.

 

<?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="match_parent"
    android:orientation="vertical">



    <Button
       android:text="rv 추가"
       android:id="@+id/btnPlus"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_margin="10dp"
       android:scaleType="centerCrop"/>


    <ScrollView
        android:layout_marginLeft="10dp"
        android:layout_marginTop="20dp"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="10"
        android:fillViewport="false">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </ScrollView>


</LinearLayout>

layout을 위처럼 꾸며 버튼을 누르면 Recycler View Item을 추가할 수 있도록 하겠습니다.


rv_item.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
    android:layout_margin="10dp"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:background="#ddd"
    tools:ignore="MissingDefaultResource">
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    <TextView
        android:layout_marginLeft="10dp"
        android:id="@+id/tvTitle"
        android:textSize="20dp"
        android:text="제목"
        android:textColor="#000"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <EditText
        android:id="@+id/tvContents"
        android:layout_marginLeft="20dp"
        android:text="내용 입력"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    </LinearLayout>
    <Space
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_weight="1"/>
    <Button
        android:id="@+id/btnSave"
        android:text="Save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

recycler view에 나타날 item 레이아웃을 만들어 줍니다.

제목과 내용을 포함하도록 하고,

내용을 저장할 Save 버튼을 만들었습니다.


Room ― 로컬 데이터 베이스


Room의 3가지 구성 요소를 구현해 주어야 합니다.

  • Entity : 데이터베이스 내의 테이블 / 데이터 모델 클래스.
  • DAO : 데이터베이스에 엑세스하는데 사용되는 메서드 / 데이터베이스에 접근해 실질적으로 데이터를 추가, 삭제를 수행하는 메서드를 포함
  • Database : 데이터베이스 홀더를 포함한 기본 엑세스 포인트 역할/ 앱에 영구 저장되는 데이터와 기본 연결을위한 주 엑세스 지점


 

 

Entitiy


Java

  • Memo.java
@Entity(tableName = "memoTable")
public class Memo {

    //Room에서 자동으로 id를 할당
    @PrimaryKey(autoGenerate = true)
    private int id;
    private String title;
    private String contents;

    public Memo(String title, String contents){
        this.title = title;
        this.contents = contents;
    }
    public int getId(){
        return id;
    }
    public void setId(int id){
        this.id = id;
    }

    public String getTitle(){
        return title;
    }
    public String getContents(){
        return contents;
    }
    public void setTitle(String title){
        this.title = title;
    }
    public void setContents(String in){
        this.contents = in;
    }

    @Override
    public String toString(){
        return "RecordData{" +
                "id='" + id + '\'' +
                ", title='" + title + '\'' +
                ", contents='" + contents + '\''
                + '}';
    }
}

 

Kotlin

  • Memo.kt
@Entity(tableName = "memoTable")
data class Memo(
    @PrimaryKey
    var id: Long?,

    @ColumnInfo(name = "title")
    var title: String?,

    @ColumnInfo(name = "contents")
    var contents: String?
)

 

title과 contents를 저장하기 위해 table name "memoTable"인 Memo Class entity를 만들었습니다.

Entity는 고유 식별자인 PrimaryKey(id)가 반드시 필요한데요.

id는 자동으로 할당해주도록 하였습니다. @PrimaryKey(autoGenerate = true)

 


Dao


Java

  • MemoDao.java
@Dao
public interface MemoDao {

    @Insert
    void insert(Memo memo);

    @Update
    void update(Memo memo);

    @Delete
    void delete(Memo memo);

    @Query("SELECT * FROM memoTable")
    LiveData<List<Memo>> getAll(); //LiveData

    @Query("DELETE FROM memoTable")
    void deleteAll();

}

 

Kotlin

  • MemoDao.kt
@Dao
interface MemoDao{
    @Query("SELECT * FROM memoTable ORDER BY id DESC")
    fun getAll(): LiveData<List<Memo>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(memo: Memo)

    @Update
    fun update(memo: Memo)

    @Delete
    fun delete(memo: Memo)

    @Query("DELETE FROM memoTable")
    fun deleteAll()
}

Dao를 만들어 주었습니다.

SQLite언어로 작성되어지는데요. Insert, Update, Delete, Query메서드를 위와 같이 작성해 주면 됩니다.

보시면 본래 List<Memo> getAll(); 메서드를 LiveData로 감싸주어, Observer로 변화를 감지할 수 있습니다.

그래서 전체 데이터에 변화가 생길때 LiveData Callback을 실행하여 UI를 업데이트 해주면 됩니다^^

뭔가 더 실행하라고 하여 할일이 늘어난것 같은데, 사용안할때보다 훨씬 간단하게 UI를 실시간으로 업데이트 할 수 있습니다.


Database


Java

  • MemoDatabase.java
@Database(entities = {Memo.class}, version = 1, exportSchema = false)
public abstract class MemoDatabase extends RoomDatabase {

    public abstract MemoDao memoDao();

    private static volatile MemoDatabase INSTANCE;

    //싱글톤
    public static MemoDatabase getDatabase(final Context context) {
        if (INSTANCE == null) {
            synchronized (MemoDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            MemoDatabase.class, "memo_database")
                            .build();
                }
            }
        }
        return INSTANCE;
    }

    //DB 객체 제거
    public static void destroyInstance() {
        INSTANCE = null;
    }


}

 

Kotlin

  • MemoDatabase.kt
@Database(entities = arrayOf(Memo::class), version = 1, exportSchema = false)
abstract class MemoDatabase: RoomDatabase(){
    abstract fun memoDao(): MemoDao
    
    companion object{
        private var INSTANCE: MemoDatabase? = null
        fun getInstance(context:Context): MemoDatabase{
            if (INSTANCE == null){
                INSTANCE = Room.databaseBuilder(
                    context,
                    MemoDatabase::class.java,
                    "memo_database")
                    .build()
            }
            return INSTANCE as MemoDatabase
        }
    }
    
}

 

Database를 생성해주었습니다.

이름은 "memo_database"로 하였습니다.

Database를 생성할때 주의할점은 아래와 같습니다.

  • database class는 추상클래스이다.
  • database 객체를 매번 생성하는건 리소스를 많이 사용하므로 싱글톤으로 해야한다.

 

저는 데이터베이스에 사용자가 처음부터 추가하도록 할것이지만,처음부터 데이터가 들어가 있길 원한다면, 여기에서 직접 채우줄수 있습니다.

Java

private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() {
    @Override
    public void onOpen(@NonNull SupportSQLiteDatabase db) {
        super.onOpen(db);

        // If you want to keep data through app restarts,
        // comment out the following block
        databaseWriteExecutor.execute(() -> {
            // Populate the database in the background.
            // If you want to start with more words, just add them.
            MemoDao dao = INSTANCE.memoDao();
            dao.deleteAll();

            Memo memo = new Memo("Hello1","memo1");
            dao.insert(word);
            word = new Memo("Hello2","hello2");
            dao.insert(word);
        });
    }
};

 

Kotlin

   private val sRoomDatabaseCallback = object : RoomDatabase.Callback() {
            override fun onOpen(db: SupportSQLiteDatabase) {
                super.onOpen(db)
                Thread {
                    val dao: MemoDao? = INSTANCE?.memoDao()
                    dao?.deleteAll()
                    //데이터 채우기
                }.start()
            }
        }

 

위처럼 dataBaseWriteExecutor에 정의된 onOpen()을 사용하면, database가 열릴때마다 할 행동을 override할 수 있습니다.

또한 onCraete()를 override하면 database가 생성될때 할 행동을 코딩할 수 있습니다.

위의 메서드(처음에 data를 모두 삭제후, 데이터 두개 추가)를 추가해 놓고,

.build()앞에 .addCallback(sRoomDatabaseCallback)를 적어주기만 하면 미리 데이터를 저장해 놓을 수 있습니다.

 

그외의 메서드도 참고하세요.

  • .fallbackToDestructiveMigration() : Entity에 Data를 추가하거나 변경하여 버전(version)을 바꿔야 할때 적어주면 Migration 메서드를 따로 만들지 않아도 됩니다.
  • .allowMainThreadQueries()를 호출하면 메인스레드에서 DB접근을 허용할 수 있지만, 데이터를 받아오는 작업이 길어질 경우 UI가 장시간 멈추기 때문에 실제 어플에서는 사용을 권장하지 않는다고 합니다.

Adapter


 

Java

  • RecyclerAdapter.java
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

    private List<Memo> items = new ArrayList<>();
    private Context mContext;
    private MemoDatabase db;

    public RecyclerAdapter(MemoDatabase db) {
        this.db = db;
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    public List<Memo> getItems() {return items;}

    @NonNull
    @Override
    public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.rv_item, viewGroup, false);
        mContext = viewGroup.getContext();
        return new ViewHolder(view);
    }
    @Override
    public void onBindViewHolder(RecyclerAdapter.ViewHolder viewHolder, int position) {

        viewHolder.onBind(items.get(position),position);

    }



    public class ViewHolder extends RecyclerView.ViewHolder {



        private TextView tvTitle;
        private TextView tvContents;
        private Button btnSave;
        private int index;

        public ViewHolder(View view) {
            super(view);

            tvTitle = view.findViewById(R.id.tvTitle);
            tvContents = view.findViewById(R.id.tvContents);
            btnSave = view.findViewById(R.id.btnSave);
            btnSave.setOnClickListener(v -> editData(tvContents.getText().toString()));

        }
        public void onBind(Memo memo, int position){
            index = position;
            tvTitle.setText(memo.getTitle());
            tvContents.setText(memo.getContents());
        }

        public void editData(String contents){
            new Thread(() -> {
                items.get(index).setContents(contents);
                db.memoDao().update(items.get(index));
            }).start();
            Toast.makeText(mContext,"저장완료", Toast.LENGTH_SHORT).show();
        }


    }

    public void setItem(List<Memo> data) {
        items = data;
        notifyDataSetChanged();
    }

}

 

Kotlin

  • RecyclerAdapter.kt
class RecyclerAdapter(val db: MemoDatabase, var items: List<Memo>?)
    : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>(){

    lateinit var mContext: Context

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
            : ViewHolder {
        val v: View = LayoutInflater.from(parent.context)
            .inflate(R.layout.rv_item,parent,false)
        mContext = parent.context
        return ViewHolder(v)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(items!!.get(position),position)
    }

    override fun getItemCount(): Int {
        return items!!.size
    }
    fun getItem(): List<Memo>?{
        return items
    }


    inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
        var index: Int? = null
        fun bind(memo: Memo, position: Int) {
            index = position
            itemView.tvTitle.setText(memo.title)
            itemView.tvContents.setText(memo.contents)
            itemView.btnSave.setOnClickListener { editData(itemView.tvContents.getText().toString()) }
        }
        fun editData(contents: String){
            Thread {
                index?.let { items!!.get(it).contents = contents };
                index?.let { items!!.get(it) }?.let { db.memoDao().update(it) };
            }.start()
            Toast.makeText(mContext,"저장완료", Toast.LENGTH_SHORT).show()
        }

    }
}

 

RecyclerView Adapter입니다.

onBind()를 통해 ViewHolder에 item을 bind해주고, 현재 position도 따로 저장하도록 하였습니다.

이는 Save 버튼 클릭시 editData()메서드를 통해 data를 수정하기 위해서 입니다.

Thread를 통해 DB에 접근해야하고, dao를 통해 db에 접근해 data update를 해주었습니다.

 

 


MainActivity


 

Java

  • MainActivity.java
public class MainActivity extends AppCompatActivity {

    private Button btnPlus;

    private RecyclerAdapter adapter;
    private RecyclerView recyclerView;
    private Paint p = new Paint();

    private MemoDatabase db;


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

        recyclerView = (RecyclerView) findViewById(R.id.rv_view);

        initSwipe();

        btnPlus.setOnClickListener(v -> {
            // title 입력 다이얼로그를 호출한다.
            // title 입력하여 리사이클러뷰 addItem
            final EditText edittext = new EditText(this);

            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("Item 추가");
            builder.setMessage("제목을 입력해 주세요.");
            builder.setView(edittext);
            builder.setPositiveButton("입력",
                    (dialog, which) -> {
                        //제목 입력, DB추가
                        if (!edittext.getText().toString().isEmpty()) {
                            new Thread(() -> {
                                Memo memo = new Memo(edittext.getText().toString(),null);
                                db.memoDao().insert(memo);
                            }).start();

                        }

                    });
            builder.setNegativeButton("취소",
                    (dialog, which) -> {
                        //취소버튼 클릭
                    });
            builder.show();

        });


        db = MemoDatabase.getDatabase(this);
        recyclerView.setHasFixedSize(true);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new RecyclerAdapter(db);
        recyclerView.setAdapter(adapter);

        //UI 갱신 (라이브데이터 Observer 이용, 해당 디비값이 변화가생기면 실행됨)
        db.memoDao().getAll().observe(this, new Observer<List<Memo>>() {
            @Override
            public void onChanged(List<Memo> data) {
                adapter.setItem(data);
            }
        });


    }

    private void initSwipe() {
        ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT /* | ItemTouchHelper.RIGHT */) {

            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                return false;
            }

            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                int position = viewHolder.getAdapterPosition();

                if (direction == ItemTouchHelper.LEFT) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            db.memoDao().delete(adapter.getItems().get(position));
                        }

                    }).start();

                }else {
                    //오른쪽으로 밀었을때.
                }
            }

            @Override
            public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {

                Bitmap icon;
                if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE ) {

                    View itemView = viewHolder.itemView;
                    float height = (float) itemView.getBottom() - (float) itemView.getTop();
                    float width = height / 3;

                    if (dX > 0) {
                        //오른쪽으로 밀었을 때

                    } else {
                        p.setColor(Color.parseColor("#D32F2F"));
                        RectF background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
                        c.drawRect(background, p);
                        /*
                         * icon 추가할 수 있음.
                         */
                        //icon = BitmapFactory.decodeResource(getResources(), R.drawable.icon_png); //vector 불가!
                        // RectF icon_dest = new RectF((float) itemView.getRight() - 2 * width, (float) itemView.getTop() + width, (float) itemView.getRight() - width, (float) itemView.getBottom() - width);
                        //c.drawBitmap(icon, null, icon_dest, p);
                    }
                }
                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            }
        };
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
        itemTouchHelper.attachToRecyclerView(recyclerView);
    }

}

 

Kotlin

  • MainActivity.kt

class MainActivity : AppCompatActivity() {

    private var adapter: RecyclerAdapter? = null
    private val p: Paint = Paint()

    private var db: MemoDatabase? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initSwipe()
        btnPlus.setOnClickListener {
            // title 입력 다이얼로그를 호출한다.
            // title 입력하여 리사이클러뷰 addItem
            val edittext = EditText(this)
            val builder: AlertDialog.Builder = AlertDialog.Builder(this)
            builder.setTitle("Item 추가")
            builder.setMessage("제목을 입력해 주세요.")
            builder.setView(edittext)
            builder.setPositiveButton("입력"
            ) { dialog, which ->
                //제목 입력, DB추가
                if (!edittext.text.toString().isEmpty()) {
                    Thread(Runnable {
                        db!!.memoDao().insert( Memo(null,edittext.text.toString(), null))
                    }).start()
                }
            }
            builder.setNegativeButton("취소"
            ) { dialog, which ->
                //취소
            }
            builder.show()
        }
        db = MemoDatabase.getInstance(this)
        rv_view.setHasFixedSize(true)
        val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this)
        rv_view.layoutManager = layoutManager

        //UI 갱신 (라이브데이터 Observer 이용, 해당 디비값이 변화가생기면 실행됨)
        db!!.memoDao().getAll().observe(this, androidx.lifecycle.Observer{
            // update UI
            adapter = RecyclerAdapter(db!!,it)
            rv_view.adapter = adapter
        })
    }


    private fun initSwipe() {
        val simpleItemTouchCallback: ItemTouchHelper.SimpleCallback = object :
            ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT /* | ItemTouchHelper.RIGHT */) {
            override fun onMove(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder,
                target: RecyclerView.ViewHolder
            ): Boolean {
                return false
            }

            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                val position = viewHolder.adapterPosition
                if (direction == ItemTouchHelper.LEFT) {
                    Thread{
                        adapter?.getItem()?.get(position)?.let { db!!.memoDao().delete(it) }
                    }.start()
                } else {
                    //오른쪽으로 밀었을때.
                }
            }

            override fun onChildDraw(
                c: Canvas,
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder,
                dX: Float,
                dY: Float,
                actionState: Int,
                isCurrentlyActive: Boolean
            ) {
                super.onChildDraw(
                    c,
                    recyclerView,
                    viewHolder,
                    dX,
                    dY,
                    actionState,
                    isCurrentlyActive
                )
                var icon: Bitmap
                if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
                    val itemView: View = viewHolder.itemView
                    val height =
                        itemView.getBottom().toFloat() - itemView.getTop().toFloat()
                    val width = height / 3
                    if (dX > 0) {
                        //오른쪽으로 밀었을 때
                    } else {
                        p.setColor(Color.parseColor("#D32F2F"))
                        val background = RectF(
                            itemView.getRight().toFloat() + dX,
                            itemView.getTop().toFloat(),
                            itemView.getRight().toFloat(),
                            itemView.getBottom().toFloat()
                        )
                        c.drawRect(background, p)
                        /*
                         * icon 추가할 수 있음.
                         */
                        //icon = BitmapFactory.decodeResource(getResources(), R.drawable.icon_png); //vector 불가!
                        // RectF icon_dest = new RectF((float) itemView.getRight() - 2 * width, (float) itemView.getTop() + width, (float) itemView.getRight() - width, (float) itemView.getBottom() - width);
                        //c.drawBitmap(icon, null, icon_dest, p);
                    }
                }

            }

        }
        val itemTouchHelper = ItemTouchHelper(simpleItemTouchCallback)
        itemTouchHelper.attachToRecyclerView(rv_view)

    }
}

이제 MainActivity.java에서 adapter와 recyclerView를 연결해줍니다.

왼쪽으로 swipe했을때 의 실행은 override한 onSwiped() 메서드를 확인해주세요.

왼쪽으로 swipe만 하였지만 오른쪽 swipe, 또 icon도 넣을 수 있습니다. 이는 주석을 확인해주세요.

왼쪽으로 swipe했을때 Db에서 item을 제거하도록 하였습니다.

new Thread(new Runnable() {
      @Override
      public void run() {
          db.memoDao().delete(adapter.getItems().get(position));
      }

}).start();

dao를 통해 db에서 item을 delete하는것 외에 아무것도 하지 않음에도, 

LiveData가 데이터 변화를 감지하여 UI를 업데이트하게 됩니다.^^ 그 소스는 onCreate에서 Oberver를 통해 onChanged를 Callback받아 실행한 코드를 보면 알수 있습니다.

 //UI 갱신 (라이브데이터 Observer 이용, 해당 디비값이 변화가생기면 실행됨)
db.memoDao().getAll().observe(this, new Observer<List<Memo>>() {
   @Override
   public void onChanged(List<Memo> data) {
     adapter.setItem(data);
   }
});

 

 

잘보셨다면 좋아요나 댓글 부탁드려요 ^^~

 

참고문서

 

728x90
반응형
LIST