도전과제 18 : 앨범의 사진을 애니메이션으로 보여주기 (Do it 안드로이드 앱 프로그래밍)[JAVA]
반응형

 

도전과제 18

단말의 앨범에 있는 사진을 가져와서 하나씩 보여주는 기능을 만들어 보세요. 각각의 사진이 보일 때는 우측에서 좌측으로 애니메이션이 적용되도록 합니다.

  1. 단말의 앨범에 들어 있는 사진 정보를 가져와서 화면에 보여줍니다.
  2. 화면에는 두 개의 사진이 한 번에 보이도록 하고 두 개의 사진이 들어 있는 패널은 애니메이션을 적용해서 다음 패널로 넘어가도록 합니다.
  3. 화면의 위쪽에는 '현재 사진의 순서/사진의 전체 개수' 정보를 표시합니다.
  4. 하나의 사진 정보에는 왼쪽에 이미지, 오른쪽에 날짜가 표시되도록 합니다.
  5. 5초마다 사진 정보가 바뀌도록 애니메이션을 설정합니다.

참고할 점
단말의 앨범에 들어 있는 사진은 내용 제공자(Content Provider)를 사용해 가져올 수 있습니다.
사진 썸네일(Thumbnail)은 패널 위에 보이도록 만들 수 있습니다.

 

풀이

역시 anim 폴더에 '우->좌'로 움직이는 애니메이션을 설정해두었다.

<!--invisible_left-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:fromXDelta="0%p"
            android:toXDelta="100%p"
            android:duration="500"
            android:repeatCount="0"/>

</set>
<!--visible_left-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="-100%p"
        android:toXDelta="0%p"
        android:duration="500"
        android:repeatCount="0"/>
</set>

레이아웃은 프레임레이아웃에 사진 두개씩 뜨도록 카드뷰를 넣었는데, 계속 돌아가는 것처럼 표현하기 위해 두 쌍으로 넣어 하나는 보이고, 다음것은 안보이고 -> 돌아가면 새 사진을 로드해서 띄워주는걸 반복하게 할 것이다.

메인 액티비티에서 스레드로 작동하는데, turn에 따라 패널을 바꿔가면서 사진을 보여주게 한다. 이때 갤러리 내의 사진을 얻어오는 방법은 getContentResolver()에 쿼리문으로 MediaStore에 접근하여 받아오면 된다.

//핸드폰 내 이미지파일 cursor로 찾기
    public void getCurse(){
        String[] projection = new String[]{
                MediaStore.Images.ImageColumns._ID, //사진의 아이디
                //MediaStore.Images.ImageColumns.DATA,	//사진 경로
                MediaStore.Images.ImageColumns.DATE_TAKEN	//사진 찍은 날짜
        };
        cursor = getContentResolver()
                .query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null,
                        null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC");	//날짜 역순으로 정렬
        totalpages = cursor.getCount()/2;
    }

Cursor는 안드로이드에서 데이터베이스에 접근할 때, 한 행씩 가르킴으로써 정보를 읽어올 수 있게 해주는 객체이다. 이제 이를 이용해서 썸네일과 날짜를 가공해야 하는데, 썸네일은 원본 사진의 ID를 이용해서 getThumbnail 메소드를 통해 받아올 수 있다.

//썸네일 띄우기
    public void getThumbnail(long id, int position){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inDither = true;
        options.inSampleSize = 5; // 이 숫자가 커질수록 사진 크기가 작아져서 속도가 빨라진다
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        bitmaps[position] = MediaStore.Images.Thumbnails.getThumbnail( getContentResolver(), id, MediaStore.Images.Thumbnails.MINI_KIND,options);
    }

날짜는 Calendar 객체를 이용하여 얻어온 시간을 Millis로 바꾸고, 정의해둔 DateFormat으로 변환시켜서 저장한다.

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

//날짜 형식
    public void getDate(long time, int position){
        cal.setTimeInMillis(time);
        Date d = cal.getTime();
        dates[position] = format.format(d);
    }

이제 이를 이용하여, 사진을 2개씩 보여지는 패널에 띄우고, 숨은 패널은 다음 사진 두장을 계속 받아오는 방식으로 어플리케이션을 구현하였다. 이때, 갤러리에 접근하기 위해서는 권한 설정이 필요하다. 책에 예제로 나왔던 권한 설정 방법을 그대로 보여주겠다. 우선 AndroidManifest.xml파일에 권한을 넣어줘야 한다.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<application
        android:requestLegacyExternalStorage="true" ...

이제 실질적으로 액티비티에서 권한을 물어보기 위해, build.gradle 파일을 수정해야 한다. 앱의 빌드 설정을 관리하는 build.gradle 파일 중 module을 관리하는 부분에 권한 요구를 위한외부 라이브러리를 추가해준다.

allprojects{
    repositories{
        maven{url 'https://jitpack.io' }
    }
}

dependencies {
	...중략
    implementation 'com.github.pedroSG94:AutoPermissions:1.0.3'
}

이제 마지막으로 메인 액티비티에서 권한을 얻어내는 AutoPermission을 사용하면 앱을 처음 시작 할 때 권한을 물어 볼 것이다. 이때 주의해야 할 사항은, 만약 Manifest에 권한을 추가 안하고 앱을 설치->실행했다가 에러가 생겨서 수정했다면, 기존의 앱을 완전히 삭제하고 재설치해야 권한이 반영된다.

public class MainActivity extends AppCompatActivity implements AutoPermissionsListener {
	... 생략
	protected void onCreate(Bundle savedInstanceState) {
    	//권한 받아오기
   		AutoPermissions.Companion.loadAllPermissions(this,101);
    }
    
    //추가적으로 AutoPermissionsListener에서 구현해야 하는 함수들. 권한을 요청한 후 결과 함수
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults){
        super.onRequestPermissionsResult(requestCode,permissions,grantResults);
        AutoPermissions.Companion.parsePermissions(this,requestCode, permissions,this);
    }

    @Override
    public void onDenied(int i, String[] strings) {
    }

    @Override
    public void onGranted(int i, String[] strings) {
    }
}

애니메이션은 이전 도전 과제와 마찬가지로 Listener에 등록해 사용하면 된다.

public class MainActivity extends AppCompatActivity implements AutoPermissionsListener {

    TextView textNum;
    boolean turn = false;
    boolean rotate = true;
    Calendar cal = new GregorianCalendar();
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

    Handler handler;
    Bitmap bitmaps[] = new Bitmap[2];
    String dates[] = new String[2];

    public ImageView imageViews[] = new ImageView[4];
    public TextView textViews[] = new TextView[4];

    LinearLayout panel1;
    LinearLayout panel2;

    Animation visibleLeft;
    Animation invisibleLeft;
    int position=1;
    int totalpages = 0;
    int pages = 0;
    Cursor cursor;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AutoPermissions.Companion.loadAllPermissions(this,101);

        imageViews[0] = findViewById(R.id.imageView1);
        imageViews[1] = findViewById(R.id.imageView2);
        imageViews[2] = findViewById(R.id.imageView3);
        imageViews[3] = findViewById(R.id.imageView4);

        textViews[0] = findViewById(R.id.textView1);
        textViews[1] = findViewById(R.id.textView2);
        textViews[2] = findViewById(R.id.textView3);
        textViews[3] = findViewById(R.id.textView4);

        panel1 = findViewById(R.id.pannel1);
        panel2 = findViewById(R.id.pannel2);
        textNum = findViewById(R.id.textNum);

        visibleLeft = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.visible_left);
        invisibleLeft = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.invisible_left);
        PanelAnimationListener animationListener = new PanelAnimationListener();
        invisibleLeft.setAnimationListener(animationListener);
        visibleLeft.setAnimationListener(animationListener);

        final MyThread myThread = new MyThread();
        handler = new Handler();
        myThread.start();

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                rotate = false;
            }
        });
    }

    class MyThread extends Thread{
        public void run(){
            //커서 얻기
            getCurse();
            while(rotate){
                if(!turn){
                    handler.post(new Runnable() {
                        @Override
                        //패널 두개를 애니메이션으로 돌리면서 그 직전에 image랑 text를 넣는 형식으로함 (더이상은 모르겠음)
                        public void run() {
                            //커서에서 PATH랑 DATE얻기
                            getLastImage();

                            //이미지 뷰, 텍스트 세팅
                            for(int i=0; i<2; i++){
                                //실제 이미지 그대로 보여주는 방법
                                //File files = new File(paths[i]);
                                //if(files.exists()){
                                   // Bitmap myBitmap = BitmapFactory.decodeFile(files.getAbsolutePath());
                                   // imageViews[i+2].setImageBitmap(myBitmap);
                                    imageViews[i+2].setImageBitmap(bitmaps[i]);
                                    textViews[i+2].setText(dates[i]);
                                //}
                            }
                            panel1.startAnimation(invisibleLeft);
                            panel2.startAnimation(visibleLeft);
                            turn = !turn;
                            textNum.setText(pages + "/" + totalpages);
                        }
                    });
                }
                else{
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            getLastImage();

                            for(int i=0; i<2; i++){
                                //File files = new File(paths[i]);
                                //if(files.exists()){
                                    //Bitmap myBitmap = BitmapFactory.decodeFile(files.getAbsolutePath());
                                    //imageViews[i].setImageBitmap(myBitmap);
                                    imageViews[i].setImageBitmap(bitmaps[i]);
                                    textViews[i].setText(dates[i]);
                                //}
                            }
                            panel2.startAnimation(invisibleLeft);
                            panel1.startAnimation(visibleLeft);
                            turn = !turn;
                            textNum.setText(pages + "/" + totalpages);
                        }
                    });
                }
                try{
                    Thread.sleep(2000);
                } catch(Exception e){};
            }
        }
    }
    //날짜 형식
    public void getDate(long time, int position){
    	//...생략
    }

    //썸네일 띄우기
    public void getThumbnail(long id, int position){
    	//...생략
    }
    //핸드폰 내 이미지파일 cursor로 찾기
    public void getCurse(){
        //...생략
    }

    //이미지뷰 ,텍스트뷰에 들어갈 내용 만들기
    public void getLastImage() {
        long datessec;
        long id;
        // Put it in the image view
        if (cursor.moveToNext()) {
            //paths[0]  = cursor.getString(1);

            id = cursor.getLong(0);
            getThumbnail(id,0); //id로 썸네일 구하기

            datessec = cursor.getLong(1);
            getDate(datessec,0);    //date구하기
        }
       if(cursor.moveToNext()){
           //paths[1]  = cursor.getString(1);
           id = cursor.getLong(0);
           getThumbnail(id,1);

           datessec = cursor.getLong(1);
           getDate(datessec,1);
        }
       pages++;
    }

    //권한승인
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults){
       //...생략
    }

    @Override
    public void onDenied(int i, String[] strings) {
    }

    @Override
    public void onGranted(int i, String[] strings) {
    }

    //애니메이션 리스너
    private class PanelAnimationListener implements Animation.AnimationListener{

        @Override
        public void onAnimationStart(Animation animation) {
            panel2.setVisibility(View.VISIBLE);
            panel1.setVisibility(View.VISIBLE);
        }
        @Override
        public void onAnimationEnd(Animation animation) {
            if(turn){
                panel1.setVisibility(View.INVISIBLE);
            }else{
                panel2.setVisibility(View.INVISIBLE);
            }
        }
        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    }
}

 

결과

전체 소스코드 : https://github.com/howtolivelikehuman/DoitAndroid/tree/master/DoitMission_18/app/src/main

 

howtolivelikehuman/DoitAndroid

Do it Android programing (JAVA). Contribute to howtolivelikehuman/DoitAndroid development by creating an account on GitHub.

github.com

 

 

반응형