본문 바로가기
Developer/Android, Java

[안드로이드] 커스텀 ListView 예제 (네이버 검색 API 결과적용)

by Doony 2019. 1. 9.

이번 포스팅에서는 커스텀 리스트뷰를 이용하여, 네이버 검색 API를 통해 얻어온 결과값을 띄워보는 예제를 포스팅하고자 한다. 

리스트뷰와, 커스텀 리스트뷰의 차이점을 설명하자면 말그대로 custom하게 꾸밀 수 있다는 점이다. 

원하는대로 이미지를 넣거나, 다양한 UI들을 구현할 수 있다는 장점이 있다.

기본적으로 custom listview를 사용하기 위해서는 다음과 같이 5개의 파일이 필요하다.

Java 

1. MainActivity.java: Custom ListView가 구현될 Activity (사용자가 보는 화면에 해당하는 java)

2. Adapter.java: listView Layout을 inflate. (Activty와 Data 사이 중간역할. 리스트뷰 xml을 받아서 메인화면에 띄워줌)

3. Data.java: listView에 들어갈 변수들을 담는 그릇. (Getter, Setter)


XML

1. activity.xml: 사용자가 보는 화면

2. listView.xml: 리스트뷰를 정의하는 화면


네이버나 구글에 자료가 정~말 많은데, 대체로 원리적인 내용들이라 이해하기가 좀 난해했다. 간단하게 생각해보면, 다음 그림처럼 생각해볼 수 있다.


더 복잡해보일 수도 있지만.. 무튼 MainActivity 안에, 커스텀리스트뷰라는 새로운 xml을 담기 위해 중간에 Adaptor을 사용했다고 보면 된다. 어댑터를 사용하지 않으면, 하나의 자바 클래스에 2개의 xml파일을 어떻게 넣을 것인가?


대충 이정도로 이해하고, 바로 코드를 살펴보도록 하자.

MainActivity.Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
 
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
 
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
 
import static android.content.ContentValues.TAG;
 
public class MainActivity extends AppCompatActivity {
 
    StringBuilder searchResult;
    BufferedReader br;
    String[] title, link, description, bloggername, postdate;
    SNSViewAdaptor mMyAdapter;
 
    private ListView mListView;
    int itemCount;
 
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activitymain);
        
        
        
        searchNaver("검색어를 여기에 입력");
 
        mListView = (ListView) findViewById(R.id.listViewSNS);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                String blog_url = mMyAdapter.getItem(position).getBloglink();
                Intent intent = new Intent(getApplicationContext(), InternetWebView.class);
                intent.putExtra("blog_url", blog_url);
                startActivity(intent);
 
            }
        });
 
 
    }
 
 
 
    public void searchNaver(final String searchObject) {
        final String clientId = "";//애플리케이션 클라이언트 아이디값";
        final String clientSecret = "";//애플리케이션 클라이언트 시크릿값";
        final int display = 5// 보여지는 검색결과의 수
 
        // 네트워크 연결은 Thread 생성 필요
        new Thread() {
 
            @Override
            public void run() {
                try {
                    String text = URLEncoder.encode(searchObject, "UTF-8");
                    String apiURL = "https://openapi.naver.com/v1/search/blog?query=" + text + "&display=" + display + "&"// json 결과
 
                    URL url = new URL(apiURL);
                    HttpURLConnection con = (HttpURLConnection) url.openConnection();
                    con.setRequestMethod("GET");
                    con.setRequestProperty("X-Naver-Client-Id", clientId);
                    con.setRequestProperty("X-Naver-Client-Secret", clientSecret);
                    con.connect();
 
                    int responseCode = con.getResponseCode();
 
 
                    if(responseCode==200) { // 정상 호출
                        br = new BufferedReader(new InputStreamReader(con.getInputStream()));
 
                    } else {  // 에러 발생
                        br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
                    }
 
                    searchResult = new StringBuilder();
                    String inputLine;
                    while ((inputLine = br.readLine()) != null) {
                        searchResult.append(inputLine + "\n");
 
                    }
                    br.close();
                    con.disconnect();
 
                    String data = searchResult.toString();
 
                    String[] array = data.split("\"");
                    title = new String[display];
                    link = new String[display];
                    description = new String[display];
                    bloggername = new String[display];
                    postdate = new String[display];
 
                    itemCount = 0;
                    for (int i = 0; i < array.length; i++) {
                        if (array[i].equals("title"))
                            title[itemCount] = array[i + 2];
                        if (array[i].equals("link"))
                            link[itemCount] = array[i + 2];
                        if (array[i].equals("description"))
                            description[itemCount] = array[i + 2];
                        if (array[i].equals("bloggername"))
                            bloggername[itemCount] = array[i + 2];
                        if (array[i].equals("postdate")) {
                            postdate[itemCount] = array[i + 2];
                            itemCount++;
                        }
                    }
 
                    Log.d(TAG, "title잘나오니: " + title[0+ title[1+ title[2]);
 
                    // 결과를 성공적으로 불러오면, UiThread에서 listView에 데이터를 추가
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            listViewDataAdd();
                        }
                    });
                } catch (Exception e) {
                    Log.d(TAG, "error : " + e);
                }
 
            }
        }.start();
 
    }
 
 
    public void listViewDataAdd() {
        mMyAdapter = new SNSViewAdaptor();
        for (int i = 0; i < itemCount; i++) {
 
            mMyAdapter.addItem(Html.fromHtml(title[i]).toString(),
                    Html.fromHtml(description[i]).toString(),
                    Html.fromHtml(bloggername[i]).toString(),
                    Html.fromHtml(postdate[i]).toString(),
                    Html.fromHtml(link[i]).toString());
 
        }
 
        // set adapter on listView
        mListView.setAdapter(mMyAdapter);
 
    }
 
 
 
 
}
 
 
cs


activitymain.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    
<LinearLayout
    android:id="@+id/listViewLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical"
    android:orientation="vertical">
 
    <ListView
          android:id="@+id/listViewSNS"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:layout_alignParentStart="true">
    </ListView>
 
 
</LinearLayout>
 
 
cs


SNSViewAdaptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

 
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
 
import java.util.ArrayList;
 
public class SNSViewAdaptor extends BaseAdapter{
 
    /* 데이터 그릇들의 집합을 정의 */
    private ArrayList<SNSViewItem> mItems = new ArrayList<>();
 
    @Override
    public int getCount() {
        return mItems.size();
    }
 
    @Override
    public SNSViewItem getItem(int position) {
        return mItems.get(position);
    }
 
    @Override
    public long getItemId(int position) {
        return 0;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
 
        Context context = parent.getContext();
 
        // 커스텀 리스트뷰의 xml을 inflate
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.listview_custom, parent, false);
        }
 
        /* 커스텀 리스트뷰 xml에 있는 속성값들을 정의 */
        TextView blog_title = (TextView) convertView.findViewById(R.id.blog_title);
        TextView blog_description = (TextView) convertView.findViewById(R.id.blog_description);
        TextView blogger_name = (TextView) convertView.findViewById(R.id.blogger_name);
        TextView post_date = (TextView) convertView.findViewById(R.id.post_date);
 
 
        /* 데이터를 담는 그릇 정의 */
        SNSViewItem snsViewItem = getItem(position);
 
        /* 해당 그릇에 담긴 정보들을 커스텀 리스트뷰 xml의 각 TextView에 뿌려줌 */
        blog_title.setText(snsViewItem.getTitle());
        blog_description.setText(snsViewItem.getDescription());
        blogger_name.setText(snsViewItem.getBloggername());
        post_date.setText(snsViewItem.getPostdate());
 
        return convertView;
    }
 
    /* 네이버 블로그 검색 중, 제목, 내용, 블로거이름, 포스팅 일자, 포스트 링크를 그릇에 담음 */
    public void addItem(String title, String description, String bloggername, String postdate, String link) {
 
        SNSViewItem mItem = new SNSViewItem();
        
        mItem.setTitle(title);
        mItem.setDescription(description);
        mItem.setBloggername(bloggername);
        mItem.setPostdate(postdate);
        mItem.setBloglink(link);
 
        /* 데이터그릇 mItem에 담음 */
        mItems.add(mItem);
 
    }
}
 
cs


listview_custom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:gravity="center_vertical"
        android:orientation="vertical">
 
        <TextView
            android:id="@+id/blog_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:maxLines="1"
            android:text="blog_title"
            android:textColor="#000000"
            android:textSize="15dp"
            android:textStyle="bold" />
 
        <TextView
            android:id="@+id/blog_description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ellipsize="end"
            android:maxLines="3"
            android:text="blog description"
            android:textSize="15dp" />
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:orientation="horizontal">
 
            <TextView
                android:id="@+id/blogger_name"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:ellipsize="end"
                android:maxLines="1"
                android:text="blogger name"
                android:textColor="#000000"
                android:textSize="12dp" />
 
            <TextView
                android:id="@+id/textView6"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginLeft="10dp"
                android:text="-" />
 
            <TextView
                android:id="@+id/post_date"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginLeft="10dp"
                android:ellipsize="end"
                android:maxLines="1"
                android:text="post date"
                android:textSize="12dp" />
 
        </LinearLayout>
 
 
    </LinearLayout>
</LinearLayout>
cs


SNSViewItem.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

public class SNSViewItem {
 
    private String title;
    private String description;
    private String bloggername;
    private String postdate;
    private String bloglink;
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public String getDescription() {
        return description;
    }
 
    public void setDescription(String description) {
        this.description = description;
    }
 
    public String getBloggername() {
        return bloggername;
    }
 
    public void setBloggername(String bloggername) {
        this.bloggername = bloggername;
    }
 
    public String getPostdate() {
        return postdate;
    }
 
    public void setPostdate(String postdate) {
        this.postdate = postdate;
    }
 
    public String getBloglink() {
        return bloglink;
    }
 
    public void setBloglink(String bloglink) {
        this.bloglink = bloglink;
    }
}
cs


우선 위 5가지 파일이 처음에 설명한, 커스텀 리스트뷰를 구성하기 위한 필수적인 부분이다. 네이버 검색 API를 사용하는 방법이 조금 들어있는데, 이 부분은 지난 포스팅을 참고하시길.


설명이 안된 부분들이 있는데, 크게 다음 2가지부분이다.


1. 네이버 검색 결과를 위 포스팅에 의해 가져오게 되면, 태그가 같이 String값에 저장되는 것을 볼 수 있다. 네이버 검색의 경우, 예를들어 검색어 "티스토리"로 검색하게 되면 검색어와 일치하는 부분이 굵은 글씨로 표시가 되는데, 굵은 글씨를 의미하는 <b> </b> 태그가 같이 나온다는 소리이다.

이를 위해MainActivity에서 Html.fromHtml(description[i]).toString()라는 코드를 삽입하여, HTML의 특성을 다 지운 후, 순수한 텍스트값만 String으로 저장했다.


2. 커스텀 리스트뷰의 특정 아이템을 클릭했을 때, OnclickListener을 사용하여 해당 블로그 웹페이지를 보여주고자 했다.  MainActivity에서 mListView.setOnItemClickListener코딩 부분을 참조하면 된다. 보면 블로그 포스팅의 link, 즉 웹 URL 정보를 intent로 넘겨주는 걸 볼 수 있는데, 이는 WebView만 띄워놓은 다른 activity 파일에 URL을 전송해주기 위함이다. WebView XML은 단순해서 생략하기로 하고, WebView에 해당하는 Activity 클래스는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
 
public class InternetWebView extends Activity {
 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        setContentView(R.layout.internet_webview);
 
        WebView webView = (WebView) findViewById(R.id.webView);
 
        Intent intent = getIntent();
        String blog_url = intent.getExtras().getString("blog_url");
        webView.getSettings().setJavaScriptEnabled(true);
        webView.loadUrl(blog_url);
        webView.setWebChromeClient(new WebChromeClient());
        webView.setWebViewClient(new WebViewClient());
 
 
 
    }
 
}
 
cs

크게 어려운 부분은 없고, WebView id값을 찾아서 저장한 후, MainActivity에서 받은 인텐트값인 URL 정보를 받는다. 그 후, webView에 loadUrl로 받아주기만 하면 된다. (그 뒤에 나오는 setWebChromeClient 등은 인터넷 창을 여는 방식에 해당하는 부분으로, 일단 동일하게 쓰면 된다)


이렇게 네이버 블로그 검색 API를, 커스텀 리스트뷰에 띄우고, 해당 아이템 클릭스 온클릭리스너까지 적용하는 방법에 대해서 알아보았다.

댓글