[Android/Java] Adapter 사용해서 ListView app 만들기 (+ ImageView에 url로 image 로드하기)

JuhyunKim·2022년 11월 12일
0

Android

목록 보기
3/3
post-thumbnail

코딩 과제로 만들었던건데 간단한 ListView app 응용 예시로 좋을 것 같아서 가져와봤다!


1. ImageView와 TextView로 이루어진 ListView 만들기

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

우선 activity_main.xml에 ListView 추가.

list_view.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:apps="http://schemas.android.com/apk/res-auto">
    <ImageView
        android:id="@+id/iv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        tools:ignore="MissingConstraints" />
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        apps:layout_constraintStart_toEndOf="@+id/iv"
        apps:layout_constraintTop_toTopOf="@+id/iv"
        apps:layout_constraintBottom_toBottomOf="@+id/iv" />
</androidx.constraintlayout.widget.ConstraintLayout>

[project]/app/src/main/res/layout 폴더에 list_view.xml 파일 생성해주고 ImageView와 TextView를 가지도록 만들어준다. 이 부분이 리스트에 들어갈 아이템 항목들인데, 나는 ImageView랑 TextView 했지만 필요에 따라 바꿔서 사용하면 된다.

ListItem.java

public class ListItem {
    private String text;
    private String imgUrl;
    public ListItem(String text, String imgUrl){
        this.text = text;
        this.imgUrl = imgUrl;
    }
    public String getText(){
        return this.text;
    }
    public String getImgUrl(){
        return this.imgUrl;
    }
}

그리고 리스트 항목에 들어갈 클래스를 따로 만들어줬는데, image를 url로 받아오는 것을 만들 예정이라 image url을 받도록 만들어놨다.


2. Adapter 생성하기, ImageView에 url로 image 넣기

ListViewAdapter.java

import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;

import java.util.ArrayList;

public class ListViewAdapter extends BaseAdapter {
    private ArrayList<ListItem> list;
    private Context context;

    public ListViewAdapter(Context context, ArrayList<ListItem> list){
        this.context = context;
        this.list = list;
    }
    @Override
    public int getCount() {
        return list.size();
    }
    @Override
    public ListItem getItem(int position) {
        return list.get(position);
    }
    @Override
    public long getItemId(int position) {
        return position;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        @SuppressLint("ViewHolder")
        View view = LayoutInflater.from(context).inflate(R.layout.list_view, null);

        ImageView iv = view.findViewById(R.id.iv);
        Glide.with(view).load(list.get(position).getImgUrl()).into(iv);

        TextView tv = view.findViewById(R.id.tv);
        tv.setText("Index "+position);
        return view;
    }
}

그리고 이 프로젝트에서 중요한 Adapter를 이런 식으로 만들었다.
여기서 화면을 구성하는 함수인 getView 부분에서 리스트 값을 잘 넣어줘야하는데, 나는 ImageView에는 url로 받아온 image를, TextView 에는 각 항목의 인덱스를 표현하도록 해뒀다.
받아온 데이터를 사용하려면 list.get(position).text 이런식으로 get을 사용해서 데이터 값을 받아와서 넣어주면 된다.

이 코드를 참고할 때 확인해야 할 점은 image를 받아오는 Glide 인데 Glide를 사용하려면 세팅이 조금 필요하다.


Glide Set up https://bumptech.github.io/glide/doc/download-setup.html

build.gradle

repositories {
    google()
    mavenCentral()
}

dependencies {
	...
    compile 'com.github.bumptech.glide:glide:4.14.2'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
}

우선 build.gradle에 google과 mavenCentral을 추가해주고, glide를 사용하기 위한 의존성을 추가해줘야한다.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jhkim.dktechin_assignment">

    // 이 밑의 permission 두개를 추가해줘야한다.
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

	...
</manifest>

그리고 url로 받아오는 것이니 permission을 추가해주면 된다.


Glide.with(fragment).load(url).into(imageView);

Glide를 사용할 땐 이 형식에 맞춰서 사용하면 되는데 현재 보여쥴 fragment와 이미지의 url, image view만 넣으면 돼서 간단하다.


3. scroll이 최하단에 갔을 때 list item 추가하기 - OnScrollListener

MainActivity.java

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.widget.AbsListView;
import android.widget.ListView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener {

    private ListView listView;

    private ArrayList<ListItem> arrayList;
    private ListViewAdapter adapter;

    private boolean loading = false;	// 화면을 로딩하는 중인지 체크하는 플래그
    private boolean scrollBottomCheck = false;		// scroll이 최하단까지 내려갔는지 체크하는 플래그
    static final int offset = 10;		// 한 페이지에 표현할 list item 개수

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

        arrayList = new ArrayList<ListItem>();

        adapter = new ListViewAdapter(this, arrayList);

        listView.setAdapter(adapter);
        listView.setOnScrollListener(this);		// ListView에서 scroll event 감지할 수 있도록 listenr 추가
        getItem();
    }

    @Override
    public void onScrollStateChanged(AbsListView absListView, int scrollState) {
        if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE && scrollBottomCheck && !loading)
            getItem();
    }

    @Override
    public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        scrollBottomCheck = (totalItemCount > 0) && (totalItemCount - visibleItemCount) <= (firstVisibleItem+visibleItemCount);
    }

    private void getItem(){
        loading = true;

        for(int i=0; i<offset; ++i)
            arrayList.add(new ListItem("","https://velog.velcdn.com/images/jhkim0122/profile/730b7aa2-225d-40c9-a564-530acf29a041/image.jpg"));

        new Handler().postDelayed(new Runnable() { // 업데이트가 잘 보이도록 delay 넣어준 것
            @Override
            public void run() {
                adapter.notifyDataSetChanged();
                loading = false;
            }
        }, 200);
    }
}

scroll을 확인하고 업데이트하기 위해 implement로 OnScrollListener를 추가해줘야한다.
그리고 onScroll에서 화면에 보이는 list item의 개수를 확인하여 최하단인지 확인한다.
onScrollStateChanged 함수에서 scroll state가 바꼈는지 확인하고 item을 추가해주면 된다.

아이템을 추가하는 함수 getItem()에서 Handler().postDelayed()를 사용해서 화면을 업데이트하고 200ms 딜레이를 줬는데, 이 부분은 업데이트 되는 부분이 눈에 잘 보이도록 지연시킨거라 저 부분을 생략하면 속도가 훨씬 빨라진다.

참고로 예시 속 image에 사용할 url은 내 벨로그 프사를 사용했다.


4. 완성

완성된 앱 움짤. 왕 많으니까 왕 귀엽다.


어댑터의 역할이 뭔지 정확히 이해하기 어려울 수도 있을 것 같아서 정의도 추가해본다.

어댑터 (Adapter)

어댑터 개체는 AdapterView해당 보기의 기본 데이터와 다리 사이의 다리 역할을 합니다. 어댑터는 데이터 항목에 대한 액세스를 제공합니다. View어댑터는 데이터 세트의 각 항목 을 만드는 역할도 합니다 .

어댑터 패턴 (adapter pattern)

어댑터 패턴(Adapter pattern)은 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴으로, 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들이 함께 작동하도록 해준다.



사담) 이전 게시글은 보통.. 개발을 진행하면서 게시글을 작성했던거라 내가 개발하는 순서에 따라 자연스럽게 진행됐었는데 다 만든걸 글로 정리해서 올리려니까 생각보다 쉽지 않다.. MainActivity가 제일 마지막에 보스마냥 등장하는게 맞나 싶어서 좀 고민했는데 그냥 그대로 올리기로 했다. 그렇다고 부가적인? 기능 느낌인 Scroll Listener를 중간에 냅다 넣을수도 없고.. 그걸 지워서 올렸다가 Scroll Listener를 추가해서 올리자니 넘 수고스럽고.. 암튼 개발응애 때에도 술술 따라할 수 있게 글 써줬던 수많은 tistory, velog 블로거들이 새삼 대단하다.

0개의 댓글