- response JSON 의 구조를 파악하여, 원하는 화면으로 구성 하시오.
- 에디티텍스트가 하나 있어서, 유저한테 검색어를 입력받습니다.
- 검색 결과는 20개씩 받아오도록 요청하여야 합니다.
- 리사이클러뷰를 꼭 적용하여, 리스트로 보여줘야 합니다.
- 썸네일 이미지를 클릭하면, 새로운 액티비티에 큰 이미지로 보여줘야 합니다.
- 리사이클러뷰의 카드뷰를 클릭하면, 웹을 실행시켜서 비디오를 보여줍니다.
응답 에서 "videoId" 의 값을 아래 url에 붙여서, 웹 뷰를 띄우시오.
https://www.youtube.com/watch?v= "videoId"
(예 https://www.youtube.com/watch?v=tnUslFhxRTs )
- MainActivity.java
- YoutubeAdapter.java
- ImageViewerActivity.java
- Youtube.java
- Config.java
▶MainActivity.java
package com.example.yutubeapp3;
import static com.example.yutubeapp3.Config.YOUTUBE_API_KEY;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.util.Log;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.example.yutubeapp3.adapter.YoutubeAdapter;
import com.example.yutubeapp3.model.Youtube;
import com.google.android.material.snackbar.Snackbar;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
RecyclerView rv;
YoutubeAdapter adapter;
ArrayList<Youtube> youtubeList;
ProgressBar pb;
ImageView imgSearch;
EditText editSearch;
String URL;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editSearch = findViewById(R.id.editSearch);
imgSearch = findViewById(R.id.imgSearch);
pb = findViewById(R.id.pb);
rv = findViewById(R.id.rv);
rv.setHasFixedSize(true);
rv.setLayoutManager(new LinearLayoutManager(MainActivity.this));
// 검색 이미지 클릭시 검색어 검색
imgSearch.setOnClickListener(view -> {
String searchURL = "https://www.googleapis.com/youtube/v3/search?part=snippet" + YOUTUBE_API_KEY;
String keyword = "&q=" + editSearch.getText().toString().trim();
String maxResultsParam = "&maxResults=20";
URL = searchURL + keyword + maxResultsParam;
if(editSearch.getText().toString().trim().isEmpty()){
Snackbar.make(imgSearch,"검색어를 입력하세요",Snackbar.LENGTH_SHORT).show();
return;
}else{
// Json 네트워크 통신 메소드
getData();
}
// 강사님은 youtubeList.clear()사용함 왜냐면 검색할때마다 관련내용이 계속 어레이리스트에 추가되기 때문이다
});
}
public void getData() {
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);
// 네트워크 통신을 위한 JsonObjectRequest 객체 생성
// 생성자 : http Method, API URL, 전달 할 데이터, 실행 코드(Listener)
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, URL, null,
response -> {
// 강사는 URL부분에 Config.HOST + Config.PATH + "?" + Config.YOUTUBE_API_KEY + "" 를 썼다
// API 호출 결과 실행
try {
youtubeList = new ArrayList<Youtube>();
JSONArray jarr = response.getJSONArray("items");
for (int i = 0; i < jarr.length(); i++) {
JSONObject jdata = jarr.optJSONObject(i);
// 유튜브 비디오 ID
JSONObject jid = jdata.optJSONObject("id");
String videoId = jid.optString("videoId");
// 유튜브 제목과 내용
JSONObject jsnippet = jdata.optJSONObject("snippet");
String title = jsnippet.optString("title");
String description = jsnippet.optString("description");
// 유튜브 썸네일 이미지 디폴트
JSONObject jthumb = jsnippet.optJSONObject("thumbnails");
JSONObject jmedium = jthumb.optJSONObject("medium");
String thumbUrl = jmedium.optString("url");
// 유튜브 썸네일 이미지 가장 큰 크기
JSONObject jhigh = jthumb.optJSONObject("high");
String thumbUrlHigh = jhigh.optString("url");
Youtube youtube = new Youtube(videoId, title, description, thumbUrl, thumbUrlHigh);
youtubeList.add(youtube);
}
adapter = new YoutubeAdapter(MainActivity.this, youtubeList);
rv.setAdapter(adapter);
adapter.notifyDataSetChanged();
} catch (JSONException e) {
Snackbar.make(imgSearch,"데이터 파싱 에러",Snackbar.LENGTH_SHORT).show();
return;
}
}, error -> {
Log.i("onErrorResponse", "" + error);
});
jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy(60000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
requestQueue.add((jsonObjectRequest));
}
}
▶YoutubeAdapter.java
package com.example.yutubeapp3.adapter;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.LazyHeaders;
import com.example.yutubeapp3.ImageViewerActivity;
import com.example.yutubeapp3.R;
import com.example.yutubeapp3.model.Youtube;
import java.util.ArrayList;
public class YoutubeAdapter extends RecyclerView.Adapter<YoutubeAdapter.ViewHolder>{
Context context;
ArrayList<Youtube> youtubeList;
Youtube youtube;
public YoutubeAdapter(Context context, ArrayList<Youtube> youtubeList) {
this.context = context;
this.youtubeList = youtubeList;
}
@NonNull
@Override
public YoutubeAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.youtube_row, parent, false);
return new YoutubeAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
youtube = youtubeList.get(position);
holder.txtTitle.setText(youtube.title);
holder.txtDesc.setText(youtube.description);
GlideUrl url = new GlideUrl(youtube.thumbUrl, new LazyHeaders.Builder().addHeader("User-Agent", "Android").build()); // 강사님은 이 부분 안씀
Glide.with(context).load(url).into(holder.imgThumb);
}
@Override
public int getItemCount() {
return youtubeList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView txtTitle, txtDesc, txtAlbumId;
ImageView imgThumb;
CardView cardView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
txtTitle = itemView.findViewById(R.id.txtTitle);
txtDesc = itemView.findViewById(R.id.txtBody);
imgThumb = itemView.findViewById(R.id.imgView);
cardView = itemView.findViewById(R.id.cardView);
imgThumb.setOnClickListener(view -> {
int index = getAdapterPosition();
youtube = youtubeList.get(index);
Intent intent = new Intent(context, ImageViewerActivity.class);
intent.putExtra("url", youtube.thumbUrlHigh);
context.startActivity(intent);
});
cardView.setOnClickListener(view -> {
int index = getAdapterPosition();
youtube = youtubeList.get(index);
String webUrl = "https://m.youtube.com/watch?v=" + youtube.videoId;
// IopenWebPage();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(webUrl));
context.startActivity(intent);
});
}
// 강사님 코드
// void openWebPage(String url){
// // 여행숙소앱이다 숙소 홈페이지로 들어갈 수 있게 한다
// Uri uri = Uri.parse(url);
// Intent intent = new Intent(Intent.ACTION_VIEW,uri);
// context.startActivity(intent);
// }
}
}
▶ImageViewerActivity.java
package com.example.yutubeapp3;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.LazyHeaders;
public class ImageViewerActivity extends AppCompatActivity {
ImageView img;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_viewer);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
img = findViewById(R.id.imgBig);
String imgUrl = getIntent().getStringExtra("url");
GlideUrl url = new GlideUrl(imgUrl, new LazyHeaders.Builder().addHeader("User-Agent", "Android").build());
Glide.with(ImageViewerActivity.this).load(url).into(img);
}
@Override
public boolean onSupportNavigateUp() {
finish();
return true;
}
}
▶Youtube.java
package com.example.yutubeapp3.model;
public class Youtube {
public String videoId, title, description, thumbUrl, thumbUrlHigh;
public Youtube(String videoId, String title, String description, String thumbUrl, String thumbUrlHigh) {
this.videoId = videoId;
this.title = title;
this.description = description;
this.thumbUrl = thumbUrl;
this.thumbUrlHigh = thumbUrlHigh;
}
}
▶ Config.java
package com.example.yutubeapp3;
public class Config {
// 강사코드
//public static final String HOST = "https://www.googleapis.com";
//public static final String PATH = "/youtube/v3/search";
public static final String YOUTUBE_API_KEY = "&key=구글 API 키";
}
▶ API응답과 코드부분 비교
{
"kind": "youtube#videoListResponse",
"etag": "\"UCBpFjp2h75_b92t44sqraUcyu0/sDAlsG9NGKfr6v5AlPZKSEZdtqA\"",
"videos": [
{
"id": "7lCDEYXw3mM",
"kind": "youtube#video",
"etag": "\"UCBpFjp2h75_b92t44sqraUcyu0/iYynQR8AtacsFUwWmrVaw4Smb_Q\"",
"snippet": {
"publishedAt": "2012-06-20T22:45:24.000Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "Google I/O 101: Q&A On Using Google APIs",
"description": "Antonio Fuentes speaks to us and takes questions on working with Google APIs and OAuth 2.0.",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/default.jpg"
},
"medium": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/mqdefault.jpg"
},
"high": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/hqdefault.jpg"
}
},
"categoryId": "28"
},
"contentDetails": {
"duration": "PT15M51S",
"aspectRatio": "RATIO_16_9"
},
"statistics": {
"viewCount": "3057",
"likeCount": "25",
"dislikeCount": "0",
"favoriteCount": "17",
"commentCount": "12"
},
"status": {
"uploadStatus": "STATUS_PROCESSED",
"privacyStatus": "PRIVACY_PUBLIC"
}
}
]
}
imgSearch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String keyword = editSearch.getText().toString().trim();
if(keyword.isEmpty()){
Snackbar.make(imgSearch,
"검색어를 입력하세요",
Snackbar.LENGTH_SHORT).show();
return;
}
videoArrayList.clear();
progressBar.setVisibility(View.VISIBLE);
RequestQueue queue = Volley.newRequestQueue(MainActivity.this);
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.GET,
Config.HOST + Config.PATH + "?key=" + Config.GCP_API_KEY + "&&part=snippet&maxResults=20&type=video&q=" + keyword,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
progressBar.setVisibility(View.GONE);
try {
JSONArray items = response.getJSONArray("items");
for(int i = 0; i < items.length(); i++){
JSONObject object = items.getJSONObject(i);
JSONObject id = object.getJSONObject("id");
String videoId = id.getString("videoId");
JSONObject snippet = object.getJSONObject("snippet");
String title = snippet.getString("title");
String description = snippet.getString("description");
JSONObject thumbnails = snippet.getJSONObject("thumbnails");
JSONObject medium = thumbnails.getJSONObject("medium");
String url = medium.getString("url");
Video video = new Video(title, description, url, videoId);
videoArrayList.add(video);
}
} catch (JSONException e) {
Snackbar.make(imgSearch,
"데이터 파싱 에러.",
Snackbar.LENGTH_SHORT).show();
return;
}
adapter = new VideoAdapter(MainActivity.this, videoArrayList);
recyclerView.setAdapter(adapter);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
progressBar.setVisibility(View.GONE);
}
}
);
queue.add(request);
}
});
▷ snippet에 title과 description이있고, thumbnails에 medium 그리고 medium에 url이 있기때문에 snippet, thumbnails를 먼저 가져온다. 그 다음은 title, description가져오고, medium 가져오고나서 url 가져오면된다.
'모바일 프로그래밍(Android Studio)' 카테고리의 다른 글
구글 맵 이용을 위한 설정 (0) | 2023.07.26 |
---|---|
Intent 활용 (0) | 2023.07.18 |
ActionBar menu 사용하는 방법 (0) | 2023.07.17 |
네트워크 통신을 통해 서버로부터 데이터 받기 (0) | 2023.07.14 |
메뉴 아이콘 이미지 만드는 방법 (0) | 2023.07.13 |