반응형
이번 포스팅은 앱에 유튜브의 검색 내용을 앱으로 출력해주는 기능을 구현합니다.
모든 기능은 이미 설명이되어있으므로 사실상 치트 시트에 가깝습니다.
기능 설계
- 검색어를 입력하고 검색(돋보기) 버튼을 누르면 유튜브에서 검색 결과를 뷰에 표시
- 썸네일을 누르면 썸네일의 큰 이미지가 새로운 액티비티에 표시
- 출력된 결과를 클릭하면 웹브라우저가 켜지며 유튜브 영상 재생
- 출력 결과를 스크롤하여 리스트의 마지막에 도달 할 경우, 검색 결과가 더 존재하면 다음 페이지로 새로고침
레이아웃 UI 설계
- MainActivity.java
- 유튜브를 검색하고 리사이클러뷰(id:rv)에 검색 결과 표시
- RecyclerViewAdapter (youtube_row.xml)
- 리사이클러뷰의 검색 결과를 출력할 어댑터 UI
- ImageViewerActivity.java
- 썸네일을 누르면 썸네일의 큰 이미지가 출력되는 액티비티
클래스 설계
MainActivity.java
- 메인 액티비티의 UI를 연결하고 유튜브를 검색하는 기능을 수행
YoutubeAdapter.java
- 리사이클러뷰의 UI를 연결하고 검색된 결과를 리스트에 출력해주는 기능을 수행하는 어댑터
Youtube.java
- 유튜브 검색시 필요한 데이터를 변수로 저장하는 클래스
ImageViewerActivity.java
- 확대된 이미지를 보여주는 UI를 연결하고 썸네일 클릭시 크게 확대된 이미지를 보여주는 기능을 수행
Config.java
- 유튜브 API 키를 보안상 직접적으로 하드코딩하지 않고 소프트 코딩화하기 위한 클래스
구현 (Android Studio)
- MainActivity.java
public class MainActivity extends AppCompatActivity {
RecyclerView rv;
YoutubeAdapter adapter;
ArrayList<Youtube> youtubeList;
String pageToken;
ProgressBar pb;
ImageView imgSearch;
EditText editSearch;
String URL;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pb = findViewById(R.id.progressBar);
editSearch = findViewById(R.id.editSearch);
imgSearch = findViewById(R.id.imgSearch);
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" + Config.YOUTUBE_API_KEY;
String keyword = "&q=" + editSearch.getText().toString().trim();
String maxResultsParam = "&maxResults=20";
URL = searchURL + keyword + maxResultsParam;
// Json 네트워크 통신 메소드
getData();
});
// 리스트가 마지막까지 스크롤 될 때 이벤트
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 리스트의 행 정보 저장
int lastPosition = ((LinearLayoutManager) rv.getLayoutManager()).findLastCompletelyVisibleItemPosition();
// 리스트의 총 행의 갯수 확인
int totalCount = rv.getAdapter().getItemCount();
// 리스트의 마지막에 도달 할 경우, 인덱스는 0부터 시작이므로 +1
if ( lastPosition+1 == totalCount ) {
// 페이지 토큰이 없으면 pass
if ( pageToken == null ) {
return;
}
// 페이지 토큰이 있으면 다음 페이지로 이동
else {
String pageTokenURL = "&pageToken=";
URL = URL + pageTokenURL + pageToken;
// Json 네트워크 통신 메소드
getData();
}
}
}
});
}
public void getData() {
pb.setVisibility(View.VISIBLE);
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);
// 네트워크 통신을 위한 JsonObjectRequest 객체 생성
// 생성자 : http Method, API URL, 전달 할 데이터, 실행 코드(Listener)
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, URL, null,
response -> {
// API 호출 결과 실행
try {
// 다음 페이지가 존재하는지 유무 확인
if (response.has("nextPageToken")) {
pageToken = response.getString("nextPageToken");
} else {
pageToken = null;
}
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 jdefault = jthumb.optJSONObject("default");
String thumbUrl = jdefault.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();
pb.setVisibility(View.INVISIBLE);
} catch (JSONException e) {
e.printStackTrace();
pb.setVisibility(View.INVISIBLE);
}
}, error -> {
Log.i("onErrorResponse", "" + error);
pb.setVisibility(View.INVISIBLE);
});
jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy(60000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
requestQueue.add((jsonObjectRequest));
}
}
- YoutubeAdapter.java
- youtube_row.xml 과 연결
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.txtDesc);
imgThumb = itemView.findViewById(R.id.imgThumb);
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;
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(webUrl));
context.startActivity(intent);
});
}
}
}
- ImageViewerActivity.java
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.imageView);
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() {
// back 버튼 설정법
// 1. finish()
// 2. 기계의 back 버튼 눌렀을때의 콜백 메소드 onBackPressed();
finish();
return true;
}
}
- Youtube.java
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
public class Config {
public static final String YOUTUBE_API_KEY = "&key=자신의 API키를 입력하세요";
}
실행 화면
- 앱 실행시 화면
- "비오는 날 듣기 좋은 노래" 를 검색
- 썸네일 클릭시 큰 이미지 출력
- 검색된 특정 결과를 클릭 할 경우, 웹 브라우저로 해당 영상 재생
- 스크롤을 맨 아래로 내렸을 경우, 다음 페이지로 새로 고침
반응형