Android(Java) - Youtube API(3) - 앱에 유튜브 검색 결과 출력하기

반응형

이전 포스팅 보러가기

 

이번 포스팅은 앱에 유튜브의 검색 내용을 앱으로 출력해주는 기능을 구현합니다.

모든 기능은 이미 설명이되어있으므로 사실상 치트 시트에 가깝습니다.


기능 설계

  • 검색어를 입력하고 검색(돋보기) 버튼을 누르면 유튜브에서 검색 결과를 뷰에 표시
  • 썸네일을  누르면 썸네일의 큰 이미지가 새로운 액티비티에 표시
  • 출력된 결과를 클릭하면 웹브라우저가 켜지며 유튜브 영상 재생
  • 출력 결과를 스크롤하여 리스트의 마지막에 도달 할 경우, 검색 결과가 더 존재하면 다음 페이지로 새로고침

레이아웃 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키를 입력하세요";
}

실행 화면

  • 앱 실행시 화면

 

  • "비오는 날 듣기 좋은 노래" 를 검색

 

  • 썸네일 클릭시 큰 이미지 출력

 

  • 검색된 특정 결과를 클릭 할 경우, 웹 브라우저로 해당 영상 재생

 

  • 스크롤을 맨 아래로 내렸을 경우, 다음 페이지로 새로 고침

반응형