[출처] 네이버 카페 - 자바/Java/C/C++ 개발자모임(Code/코드인)
[원문] http://cafe.naver.com/javacircle/104093

★행복한 연말을 위한★ 길벗 신간 3종 도서 나눔 이벤트

안녕하세요?

길벗출판사입니다^^


회사 송년회와 각종 모임으로 바쁘시지요?

따뜻한 연말 보내시라고 길벗 전문서 3종을 나누어드립니다. 


《카이 호스트만의 코어 자바 8》

이제 고전이 된 《Core Java™》가 핵심만 간추려 담은 Impatient 콘셉트로 다시 태어났다. 이 책은 데이터 타입이나 기초 문법, 객체 지향 프로그래밍은 100쪽 내로 핵심만 짚고 넘어간다. 이어서 자바 8에 추가된 람다 표현식을 비롯하여, 스트림, 병행 프로그래밍, 날짜와 시간 같은 최신 라이브러리에 관한 내용도 담았다. 단순히 자바 8에 추가된 개념과 특징을 기존 자바에 덧붙인 것이 아니라 기본 문법부터 라이브러리까지 현대 자바의 시각으로 자바의 모든 면을 배울 수 있게 해준다.

*도서 소개 ▶ http://bit.ly/1kbJcDS


웹 엔지니어의 교과서

현업 웹 엔지니어가 신입사원이 꼭 알아두길 바라는 내용을 다룬다. 웹 엔지니어가 어떤 일을 하는지부터 프런트 엔드, 서버사이드 기술까지 골고루 담았으므로 신입사원 교육에도 사용할 수 있다.

*도서 소개  http://bit.ly/1OhCPXA

 

Gradle 철저 입문

그레이들(Gradle)은 차세대 빌드 툴로 주목받는 오픈 소스 제품이다. 이 책은 그레이들의 모든 것을 다루는 국내 첫 그레이들 바이블이다. 그레이들 설치부터 자바 프로젝트 빌드, 빌드 스크립트 작성, 의존관계 관리, 테스트 자동화, 안드로이드 프로젝트 적용, 빌드 결과물 퍼블리싱, CI 서버 연동, 다른 빌드 툴로 이식하는 방법까지 다양한 예제와 그림을 사용해서 자세히 설명한다. 그레이들의 기본 개념은 물론, 실제 업무에서 그레이들을 적용할 때 참고할 수 있는 실용적인 내용들로 구성하여 그레이들을 처음 사용하는 사람부터 이미 사용하고 있는 사람까지 곁에 두고 필요할 때마다 찾아볼 수 있다.

*도서 소개  http://bit.ly/1IOEqIi


*참여 방법

이벤트 기간 동안 원하는 도서 1권의 제목과 받고 싶은 이유를 간략하게 작성하신 후, 본 게시물과 신청하신 도서 소개 링크(위의 url 주소)를 본인의 블로그나 SNS 등에 퍼가서 게시해주시면 됩니다.

게시한 곳을 덧글로 함께 남겨주시면 당첨 확률이 더 높아지겠죠^^


*추첨 방식 

댓글을 다신 분 중 총 15명을 추첨하여 도서를 보내드립니다(각 도서당 5명).



*이벤트 기간

2015년 12월 22일(화)~2015년 12월 31일(목)까지



*당첨자 발표

2016년 1월 4일(월)


*서평 등록

당첨되신 분들은 책을 배송 받은 후 교보문고, Yes24, 알라딘, 반디앤루니스, 영풍문고, 네이버 중에 2곳 이상 서평을 남겨주세요.

서평을 남기시고 본 게시물의 댓글에 링크를 달아주세요^^


  Custom Gallery를 동작하도록 만들기는 했지만 scroll을 상하로 바꿔서 격자 구조로 사진의 thumbnail을 보는 게 편할 것 같다. 찾아보니 GridView라는 layout이 따로 있었다. Android API reference에 따르면 애초에 Grid View 자체가 2차원으로 item을 보여주는(이 말은 즉, Main이 되는 View위에 다른 View를 여러 개 보여주는) scroll이 가능한 Viewgroup이라고 한다. 이전에 만들었던 앱에서 Gallery를 GridView로 바꾸어 구연해 보자. 

1. 목적 : GridView에 대해 알아보고 이를 이용한 Image Gallery(List)를 만들어보자

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - Grid View Example (http://developer.android.com/intl/ko/guide/topics/ui/layout/gridview.html)
 2) AndroidHive - Android GridView Layout Tutorial (http://www.androidhive.info/2012/02/android-gridview-layout-tutorial/)

4. 과정
 1) Main이 되는 Layout(여기서는 activity_main.xml)에 <GridView ...></GridView>를 다음과 같이 선언한다. 자동 완성 scroll 밑으로 <GridLayout>도 볼 수 있으나 minSdkVersion이 높으므로 낮은 사양에서도 사용할 수 있도록 <GridView>를 사용하기로 한다.

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/gridview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"
    android:columnWidth="90dp" <!-- columnWidth(나눠진 칸 안에 있는 View의 가로 길이)에 따라 column(열)의 갯수가 달라진다 -->
    android:numColumns="auto_fit" <!-- 임의로 숫자 지정 가능 -->
    android:verticalSpacing="10dp"
    android:horizontalSpacing="10dp"
    android:stretchMode="columnWidth"
    android:gravity="center">

</GridView>

 

 2) GridView 내부에 ImageView 들을 뿌릴 Adapter를 다음과 같이 정의한다. 이전의 Adapter 부분과 비슷하다.

    // 이상 생략
   
// create a new ImageView for each item referenced by the Adapter
   // 참고자료 내 Android Developers에 게시된 Example 수정

    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageView;
        if (convertView == null) {
            // if it's not recycled, initialize some attributes
            imageView = new ImageView(mContext);
        } else {
            imageView = (ImageView) convertView;
        }
        bm = BitmapFactory.decodeFile(mBasePath + File.separator + mImgList[position]);
        Bitmap mThumbnail = ThumbnailUtils.extractThumbnail(bm, 300, 300);
        imageView.setPadding(8, 8, 8, 8);
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setLayoutParams(new GridView.LayoutParams(GridView.LayoutParams.MATCH_PARENT, GridView.LayoutParams.MATCH_PARENT));
        imageView.setImageBitmap(mThumbnail);
        return imageView;
    }
    // 이하 생략

 

 3) MainActivity.java 에서 앞서 정의한 GridView layout과 Adapter를 연결하고 Item Click 시 adapter에서 넘어온 해당 ImageView의 주소 값을 Toast message로 보여주도록 다음과 같이 설정한다.


public class
MainActivity extends AppCompatActivity {

    public String basePath = null;
    public GridView mGridView;
    public CustomImageAdapter mCustomImageAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "MyCameraApp");

        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");
            }
        }

        basePath = mediaStorageDir.getPath();

        mGridView = (GridView)findViewById(R.id.gridview); // .xml의 GridView와 연결
        mCustomImageAdapter = new CustomImageAdapter(this, basePath); // 앞에서 정의한 Custom Image Adapter와 연결
        mGridView.setAdapter(mCustomImageAdapter); // GridView가 Custom Image Adapter에서 받은 값을 뿌릴 수 있도록 연결
        mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(getApplicationContext(), mCustomImageAdapter.getItemPath(position), Toast.LENGTH_LONG).show();
            }
        });
    }
}

 

(위 Code의 실행 결과)

  

      

 

  요즘에 만들고 있는 Custom Camera & Gallery App에서 Gallery의 갱신(update)가 잘 되지 않는 문제를 해결하기 위해서 이전까지는 Activity의 생애주기(Life-cycle)를 살펴봤다. 이를 통해서 Camera Intent 실행 후 넘어왔을 때 Gallery의 배열이 update 되도록 하는 문제는 해결되었으나, 파일 삭제를 위해 따로 Intent 만들어 불러오기는 작동은 되나 쓸데없이 군더더기가 많은 듯 하였다. 그래서 gallery update 에 대하여 자료를 찾아봤더니 애초에 adapter class에 있는 notifyDataSetChanged() method를 활용하면 되는 것이었다(이걸 모르고 괜히 헛짓거리를 했네 쿨럭;;). 그런데 이전에 ListView도 그렇고 Adapter를 여기저기 활용하는데, 대체 Adapter란게 무엇인가에 대하여 제대로 살펴봐야 겠다.

1. 목적 : Adapter에 대하여 알아보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - Adapter Reference (http://developer.android.com/reference/android/widget/Adapter.html)

4. Adapter에 대한 이해
 1) Adapter란 무엇인가?
   Android APIs' Reference에 따르면 'Adapter' 자체는 하나의 Object(객체)로서, 보여지는 View와 그 View에 올릴 Data를 연결하는 일종의 Bridge라고 한다. 그래서 그런지 ListView Adapter에 대한 예시에서도, GalleryAdapter에 대한 예시에서도 해당 Adapter의 생성자에서 Data를 연결하고 나서 getView() method에서 해당 position에 올라갈 TextView나 ImageView를 setting 해 View로서 뿌려주는 모양이다.

[사진 1] 기본 Adapter를 상속받는 subclass들 목록이다.

 2) Adapter 에 속해 있는 method들

 
[사진 2] Adapter의 기본 public method list

  Adapter class에 속해 있는 기본 method 의 목록은 위 [사진 2]와 같다. getCount()를 통해서 Adapter가 보여주는 item의 갯수값을 int로 받을 수 있고, getItem(int position)을 통해서 position에 위치해 있는 item의 data를 받을 수도 있다. getItemId(int postion)의 경우에는 getItem(int position)과 비슷하게 position에 위치한 값을 반환하기는 하나 그 값이 Item마다 부여된 일종의 ID라고 한다. 그리고 화면에 뿌려주기 위해서 getView(int position, View convertView, ViewGroup parent)를 활용하고, 이 getView()를 통해 생성된 View의 갯수를 getViewTypeCount()로 받을 수 있다고 한다. 이 외에도 hasStableIds()를 통해서 각 Row마다 부여된 ID를 변화에 상관없이 고정할지(stable) 여부를 결정할 수 있으며, 해당 Adapter가 비었는지를 isEmpty()로 판단할 수 있다.

  한편, notifyDataSetChanged()와 같은 기능을 활용하기 위해서는 Adapter에 묶여있는 Item의 변경 여부를 감지해야 하는데 이를 감지하는 것이 말그대로 'DataSetObserver'이다. 따라서, item의 변경이 감지(notify)되도록 하기 위해서는 registerDataSetObserver(DataSetObserver observer)를 통해서 observer를 Adapter에 연결해야 한다.

5. 적용 예시

 1) CustomGalleryAdapter.java 내 registerDataSetObserver() 등의 추가


public class
CustomGalleryAdapter extends BaseAdapter {
    int CustomGalleryItemBg;
    String mBasePath;
    Context mContext;
    String[] mImgs;
    Bitmap bm;
    DataSetObservable mDataSetObservable = new DataSetObservable(); // DataSetObservable(DataSetObserver)의 생성

    public String TAG = "Gallery Adapter Example :: ";

    public CustomGalleryAdapter(Context context, String basepath){
        this.mContext = context;
        this.mBasePath = basepath;

        File file = new File(mBasePath);
        if(!file.exists()){
            if(!file.mkdirs()){
                Log.d(TAG, "failed to create directory");
            }
        }
        mImgs = file.list(); 

        TypedArray array = mContext.obtainStyledAttributes(R.styleable.GalleryTheme);
        CustomGalleryItemBg = array.getResourceId(R.styleable.GalleryTheme_android_galleryItemBackground, 0);
        array.recycle();
    }

    @Override
    public int getCount() {
        File dir = new File(mBasePath);
        mImgs = dir.list();
        return mImgs.length;
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    // Adapter 내 Item에서 직접 주소를 받아오도록 method 추가.
    // 이전에는 MainActivity와 주소 및 position이 달라 비정상적인 앱의 종료가 발생한 것으로 보인다

    public String getItemPath(int position){ 
        String path = mBasePath + File.separator + mImgs[position];
        return path;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    // Override this method according to your need
    @Override
    public View getView(int index, View view, ViewGroup viewGroup)

    {
        // TODO Auto-generated method stub
        ImageView i = new ImageView(mContext);

        File dir = new File(mBasePath);
        mImgs = dir.list();
        bm = BitmapFactory.decodeFile(mBasePath+ File.separator +mImgs[index]);

        Bitmap bm2 = ThumbnailUtils.extractThumbnail(bm, 300, 300);
        i.setLayoutParams(new Gallery.LayoutParams(300, 300));
        i.setImageBitmap(bm2);
        i.setVisibility(ImageView.VISIBLE);

        i.setBackgroundResource(CustomGalleryItemBg);
        i.setScaleType(ImageView.ScaleType.FIT_CENTER);

        if (bm != null && !bm.isRecycled()) {
            bm.recycle();
        }
        return i;
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer){ // DataSetObserver의 등록(연결)
        mDataSetObservable.registerObserver(observer);
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer){ // DataSetObserver의 해제
        mDataSetObservable.unregisterObserver(observer);
    }

    @Override
    public void notifyDataSetChanged(){ // 위에서 연결된 DataSetObserver를 통한 변경 확인
        mDataSetObservable.notifyChanged();
    }
}

 

 2) MainActivity.java에 적용

// 파일 삭제 후 notifyDataSetChanged() 적용
        builder.setPositiveButton("예", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                        File file = new File(delFilePath);
                        file.delete();
                        File temp = new File(basePath);
                        imgs = temp.list();
                        customGalAdapter.notifyDataSetChanged();
                    }
            });

 

(위 Code의 실행 결과)

     

      

 

  앞선 글에서 Activity 생애주기(Life Cycle)를 고려하여 gallery와 gallery adapter를 onCreate()가 아니라 onStart()에 생성 및 연결을 했을 경우, camera intent 호출 후 새로 찍은 사진이 gallery에 정상 반영됨을 확인했다. 이를 보면서 사진을 삭제한 후에도 onStart()를 불러온다면 문제가 해결되겠다는 생각이 들었는데, 이를 구현하기 위해서 사진을 삭제하는 과정 자체를 새 intent로 불러왔다가 종료하여 MainActivity가 onRestart()가 되도록 해야했다. 그래서 이번에는 App. 내부에 새로운 activity를 추가해 intent로 활용하는 방법을 알아봤다. 

1. 목적 : Intent를 만들어 activity의 작동 구조를 이해하여 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - Intent Reference (http://developer.android.com/reference/android/content/Intent.html)
 2) Android Developers - 인텐트 및 인텐트 필터 (http://developer.android.com/intl/ko/guide/components/intents-filters.html)

4. 과정
 일반적인 Intent 생성 및 작동 예제에 내가 필요한 기능을 추가했다.

 1) 새로 추가할 intent의 layout이 될 .xml 파일을 다음과 같이 '/layout' directory 밑에 생성한다. 필요에 따라 layout_main.xml을 활용하듯이 Button, ImageView, TextView 등을 추가하여 새로운 activity를 구성하면 된다.

 

2) Intent가 될 새 Activity를 생성한다. 'extends Activity'한 class java file을 다음과 같이 '/java' 밑에 만들고 MainActivity에서 onCreate() 내부에 내용을 쓰듯이 원하는 동작을 작성하면 된다. 나 같은 경우에는 단순히 AlertDialog를 사용하기 위해 intent를 불러오므로 따로 setContentView(R.layout.'intent layout file 명')하지 않아도 동작은 했었으나 일단 다음과 같이 코딩하였다.

public class FileRearrange extends AppCompatActivity{

    String delFilePath = null; // MainActivity에서 삭제할 file의 경로를 받을 String 변수 선언

    @Override
    protected void onCreate(Bundle savedIntanceState){
        super.onCreate(savedIntanceState);
        setContentView(R.layout.activity_file_rearrange);

        final Intent receiveIntent = getIntent(); // MainActivity에서 intent를 받음
        delFilePath = receiveIntent.getStringExtra("delFilePath"); // MainActivity의 intent에서 추가한 data 참조
        /* 이하 AlertDialog 활용 */
        AlertDialog.Builder builder = new AlertDialog.Builder(FileRearrange.this);
        builder.setTitle("파일을 삭제하시겠습니까?");
        builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss(); // 해당 AlertDialog의 dismiss() 및
                finish(); // receiveIntent의 finish();
            }
        });
        builder.setPositiveButton("예", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                File file = new File(delFilePath);
                file.delete(); // receive 받은 data를 활용해 file 삭제 후
                finish(); // receiveIntent의 finish();
            }
        });
        Dialog dialog = builder.create();
        dialog.show();
    }
}

 

 3) AndroidMainfest.xml 에 (2)에서 만든 Activity를 <application>...</application> 내부에 다음과 같이 <activity>...</activity>하여 선언한다. 이를 통해 App. 내부 Activity같의 communication이 가능해 진다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.zip.customcameragalleryexample" >

    <uses-feature android:name="android.hardware.camera2" android:required="false" />

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


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:hardwareAccelerated="false">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- 이하 activity 추가 부분 -->
        <activity android:name=".FileRearrange"> <!-- 위의 activity class명과 동일 -->
            <intent-filter>
                <action android:name="com.example.zip.customcameragalleryexample.filerearrange"/> <!-- intent 생성시 참조 -->
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <!-- 이상 activity 추가 부분 -->
    </application>

</manifest>

 

 4) MainActivity.java 에서 파일을 삭제하고자 할 때 FileRearrange로 넘어가도록 다음과 같이 intent를 생성 및 추가한다. Intent 의 생성 → 필요할 경우 data 추가 → 해당 Intent의 start 순서로 넘어감을 이해하면 된다.

// CustomGallery 예시의 LongClickListener 부분을 다음과 같이 수정하였다.
      customGallery
.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {

                final int tmpPosition = position;
                final String delFilePath = basePath + File.separator + imgs[tmpPosition];
                Intent intent = new Intent(MainActivity.this, FileRearrange.class);
                intent.putExtra("delFilePath", delFilePath);
                startActivity(intent);
                return false;
            }
        });

 

(위 Code의 실행 결과)
  원하는 동작이 오류 없이 실행되긴 했으나, 새로 intent를 불러오니 MainActivity 가 보이지 않았다. 그래서 OnItemLongClick 내부에서 file 삭제 후 다시 gallery를 불러오도록 해당 method를 재선언을 하자니 이 방법은 재귀적이라 app 자체가 비정상적으로 종료해서 쓸 수가 없었다. 아무래도 Adapter에서 배열의 변화를 감지하여 update가 되도록 notifyDataSetChanged() 와 같은 adapter의 method들을 다시 살펴볼 필요가 있어 보인다.

     

  

 

  사실 일전의 Custom Gallery Example에서 Gallery Item Long Click시 해당 사진 파일을 삭제하는 코드를 입력했었다. 실행 결과, 파일은 삭제되기는 했지만 파일 삭제 후의 Gallery Item들의 배열이 정리되지 않았고, 원래 해당 파일이 있던 frame 내부가 null 값으로 까맣게 보이며 click 시 App.이 비정상적으로 종료되었다. 그리고 Camera Intent로 촬영 후 새로 찍은 사진이 Gallery에 넘어오지 않았다. 아무래도 file 배열이 변경된 후 Gallery Adapter를 다시 불러와야 될 것 같은데, 원래 adapter가 생성되어 있던 onCreate()를 통째로 다시 불러들여도 괜찮을지 Activity 생애주기(Life Cycle)에 대해 알아봐야 겠다.

1. 목적 : Android Activity LifeCycle(Activity 생애주기)를 이해하고 기본 구조를 알아보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - Activity Reference (http://developer.android.com/reference/android/app/Activity.html)

4. Activity Life Cycle(Acitivity 생애 주기)의 이론

  Android Studio로 App. project를 생성해 보면 /java/'해당 packaging 명'/MainActivity.java 이 기본으로 만들어져 있고 그 안에 파일명과 같은 class가 'extends Activity' 혹은 'extends AppCompatAcitivity'을 끌고 기본 method인 onCreate(Bundle savedInstanceState)를 @Override한 채로 만들어져 있는 광경을 볼 수 있다. 이 모습을 보다보면 아무래도 C 언어에서 void main(void) 가 생성되며 시작 지점이 자동으로 지정되듯이 onCreate()가 필수적 임을 눈치로 대강 알 수 있을텐데, 실제로 'Android Developers reference'를 찾아보면 다음과 같은 순서도를 통해서 App.의 Activity에 대한 동작 구조를 파악할 수 있다.

  보시다시피 App.이 실행되면서 Activity가 시작되면 처음에 onCreate()를 통해서 Activity가 생성되고 onStart()와 onResume()을 거처 해당 Activity가 실행된다. 그러다가 해당 Activity를 sleep 시킬 때 onPause()와 onStop()을 거치게 되고, onStop() method 시점에서 사용자가 해당 Activity를 다시 실행시키고자 하면 onRestart()를, App.이 memory를 많이 잡아먹어 시스템에 의해 process kill이 됐을 때 사용자가 원할 경우 onCreate()로 넘어간다. 이런 method들을 돌고 돌던 Activity는 끝날 때가 되면 onDestory()를 통해 system에서 사라진다.

  그래서 App.의 activity의 생애주기에 따라 작동해야 할 특정한 기능이 있다면 아래와 같이 Activity class 내에 위 method()를 따로 정의해서 사용한다. 하나의 예로서, 이전에 항상 꺼지지 않는 App.을 만들기 위해서 onStop() 을 @Override하여 onStop() 내에서 onRestart()를 무조건 실행시키게 만들었었다.

public class Activity extends ApplicationContext {
    protected void onCreate(Bundle savedInstanceState);

    protected void onStart();

    protected void onRestart();

    protected void onResume();

    protected void onPause();

    protected void onStop();

    protected void onDestroy();
}

  그럼 이번 Gallery가 새로 찍은 사진이 update되지 않는 문제는 Gallery Adapter의 생성 및 연결을 onStart() method에 선언하여 @Override 해보면 되지 않을까? 아무래도 Camera Intent를 불러오면서 기존 MainActivity는 onStop()에 있어 intent가 종료되고 나면 onStart() 부터 다시 시작할 것 같았기 때문이다.

5. 실험

  이전의 Custom Gallery Example의 Gallery Adapter 선언 부분을 단순히 onStart()로 아래와 같이 옮겨봤다.

public class MainActivity extends AppCompatActivity {

    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
    public int inSampleSize = 1;

    private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
    private Uri fileUri;
    private static String basePath;

    public float imageViewRotation = 90;
    public String TAG = "Camera Example :: ";

    private Button takePicBtn;
    private ImageView resultView;
    private TextView imgPath;
    private Gallery customGallery;
    private CustomGalleryAdapter customGalAdapter;

    private String[] imgs;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "MyCameraApp");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.

        // Create the storage directory if it does not exist
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");

            }
        }

        basePath = mediaStorageDir.getPath();

        imgPath = (TextView)findViewById(R.id.imgpath);
        resultView = (ImageView)findViewById(R.id.resultview);
        takePicBtn = (Button)findViewById(R.id.takepicbtn);
        takePicBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // create Intent to take a picture and return control to the calling application
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

                fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image
                intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name

                // start the image capture Intent
                startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
            }
        });

       
        customGallery = (Gallery)findViewById(R.id.customgallery);


    }

    /* 아래와 같이 onStart()를 Override하여 Gallery Adapter 와 연관된 부분을 그대로 복사해 옮겨놓았다. */
    @Override
    protected void onStart(){
        super.onStart();

        File file = new File(basePath);
        imgs = file.list();
        for(int i=0; i<imgs.length; i++){
            imgPath.setText(imgs[i]);
        }

        customGalAdapter = new CustomGalleryAdapter(getApplicationContext(), basePath);
        customGallery.setAdapter(customGalAdapter);
        customGallery.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Bitmap bm = BitmapFactory.decodeFile(basePath+ File.separator +imgs[position]);
                Bitmap bm2 = ThumbnailUtils.extractThumbnail(bm, bm.getWidth() / inSampleSize, bm.getHeight() / inSampleSize);
                resultView.setImageBitmap(bm2);
                imgPath.setText(basePath+File.separator+imgs[position]);
            }
        });
        customGallery.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                File temp = new File(basePath+File.separator+imgs[position]);
                temp.delete();
                return false;
            }
        });
    }

  위 변경사항을 반영하여 compile/Run을 시켜봤더니 다행히 camera intent 실행 후 새로 찍은 사진이 gallery 에 반영되었다. 이제는 파일 삭제 후 Gallery update가 되도록 다른 method를 살펴봐야겠다.

(위 code의 실행결과)

     

 

  앞의 예시가 기본 AlertDialog의 Method를 사용한 경우라면 이번에는 이전의 ListView와 같이 LayoutInflater를 활용하여 Custom Layout을 AlertDialog에 입혀서 활용해 보자. 실제 App. 개발시에 중요한 부분은 아니겠지만 이왕이면 여러가지 사용법을 알면 좋지 않을까 싶다.

1. 목적 : Custom Layout을 LayoutInflater를 통해 AlertDialog에 반영해 알림창을 만들어 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - AlertDialog Reference (http://developer.android.com/reference/android/app/AlertDialog.html)
 2) 창조적 고찰 - Android 커스텀 다이얼로그 생성 (http://ismydream.tistory.com/107)

4. 과정
  이전의 'AlertDialog를 활용한 알림창 띄우기 01 - 기본'편에 살을 덧붙여 만들어 봤다.

 1) AlertDialog가 받아들일 Custom View에 해당하는 layout 파일을 /layout directory 밑에 생성한다.

<!-- /layout/custom_alert_layout.xml -->
<?
xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="30dp">

    <ImageView
        android:id="@+id/customdialogicon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>
    <TextView
        android:id="@+id/customtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/customdialogicon"
        android:padding="10dp"
        android:text="Hello World!"/>
</RelativeLayout>

 

 2) AlertDialog를 띄울 activity에 LayoutInflater를 통해 위의 layout file을 연결시키고, 해당 사항을 AlertDialog.Builder의 setView(View view)을 통해 반영한다.

public class MainActivity extends AppCompatActivity {

    public ImageButton imgBtn;
    public TextView returnVal;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imgBtn = (ImageButton)findViewById(R.id.imgbtn); // ImageButton의 activity 내 생성 및 연결
        returnVal = (TextView)findViewById(R.id.returnval);
        imgBtn.setOnClickListener(new View.OnClickListener() { // ImageButton을 Click시 AlertDialog가 생성되도록 아래과 같이 설계
            @Override
            public void onClick(View v) {
                // LayoutInflater를 통해 위의 custom layout을 AlertDialog에 반영. 이 외에는 거의 동일하다.
                LayoutInflater inflater = (LayoutInflater)getApplicationContext().getSystemService(LAYOUT_INFLATER_SERVICE);
                View view = inflater.inflate(R.layout.custom_alert_layout, null);
                TextView customTitle = (TextView)view.findViewById(R.id.customtitle);
                customTitle.setText("종료하시겠습니까?");
                customTitle.setTextColor(Color.BLACK);
                ImageView customIcon = (ImageView)view.findViewById(R.id.customdialogicon);
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setView(view);
                builder.setPositiveButton("네", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                });
                builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        returnVal.setText("Custom AlertDialog를 통해서 return");
                        dialog.dismiss();
                    }
                });
                AlertDialog dialog = builder.create();
                dialog.show();

            }
        });
    }
}

(위 code의 실행 결과)

  

  

 

  어제는 PopupWindow를 통해 main이 되는 View 에 말풍선처럼 popup을 띄우는 방식을 살펴보았다. 그런데 기존 App.들에서는 안내 메시지 창 자체가 따로 위에 뜨는 느낌이었던 터라 뭐 다른게 있나 싶어 찾아봤더니, AlertDialog를 사용한 것이었다. 이번엔 AlertDialog.builder를 통해 메시지 창을 만들어 동작시켜 보자.

1. 목적 : AlertDialog 의 사용법을 익히고 App.에 적용해 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - AlertDialog Reference (http://developer.android.com/reference/android/app/AlertDialog.html)
 2) 남시언의 문화지식탐험 - AlertDialog 생성 예제 (http://namsieon.com/313)

4. 과정
 1) 어제와 같이 AlertDialog를 띄울 ImageButton와 return 여부를 확인할 TextView를 activity_main.xml 에 생성한다. AlertDialog에서 돌아왔을 때 return 여부를 보여주기 위해서 TextView의 text는 비워둔다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <ImageButton
        android:id="@+id/imgbtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="15dp"
        android:src="@mipmap/ic_launcher"/>
    <TextView
        android:id="@+id/returnval"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/imgbtn"/>
</RelativeLayout>

 

 2) App.의 전체적인 activity를 구성하는 MainActivity.java에 위의 ImageButton과 연결할 변수를 생성 및 연결하고, 해당 Button을 Click하면 AlertDialog 가 나타나도록 다음과 같이 설계한다.

public class MainActivity extends AppCompatActivity {

    public ImageButton imgBtn;
    public TextView returnVal;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imgBtn = (ImageButton)findViewById(R.id.imgbtn); // ImageButton의 activity 내 생성 및 연결
        returnVal = (TextView)findViewById(R.id.returnval);
        imgBtn.setOnClickListener(new View.OnClickListener() { // ImageButton을 Click시 AlertDialog가 생성되도록 아래과 같이 설계
            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); // AlertDialog를 띄울 activity를 argument로 지정해야 한다.
                builder.setTitle("앱을 종료하시겠습니까?"); // AlertDialog.builder를 통해 Title text를 입력
                builder.setPositiveButton("네", new DialogInterface.OnClickListener() { // AlertDialog.Builder에 Positive Button을 생성
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish(); // App.의 종료. Activity 생애 주기 참고
                    }
                });
                builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() { // AlertDialog.Builder에 Negative Button을 생성
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        returnVal.setText("앱을 종료하지 않고 돌아왔음");
                        dialog.dismiss(); // "아니오" button이 받은 DialogInterface를 dismiss 하여 MainView로 돌아감
                    }
                });
                AlertDialog dialog = builder.create(); // 위의 builder를 생성할 AlertDialog 객체 생성
                dialog.show(); // dialog를 화면에 뿌려 줌
            }
        });
    }
}

(위의 code 실행 결과)

   

  

 

1. 목적 : PopupWindow의 사용법을 익히고 App.에 적당한 위치에 적용해 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - PopupWindow Reference (http://developer.android.com/reference/android/widget/PopupWindow.html)
 2) App. 제작소 - [Android] PopupWindow 사용 예제 (http://makingappfor.blogspot.kr/2013/05/android-popupwindow.html)
 3) Android-er Blog - Display Spinner inside PopupWindow (http://android-er.blogspot.kr/2013/08/display-spinner-inside-popupwindow.html)

4. 과정
  PopupWindow example을 요약하자면 Popup window가 될 .xml layout을 구성하여 LayoutInflator로 불러 들여 현재 PopupWindow 변수를 통해 View 위에 그려주는 순서로 진행된다.

 1) activity_main.xml(main의 layout)에 클릭시 Popup Window를 불러들일 ImageButton을 추가한다.


<?
xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <ImageButton
        android:id="@+id/exampleimg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:padding="20dp"
        android:src="@mipmap/ic_launcher"/>
</RelativeLayout>

 

 2) PopupWindow의 layout이 될 .xml file을 생성한다. 나 같은 경우에는 App의 종료 여부를 물을 창을 만들 것이므로 TextView 1개와 Button 2개를 다음과 같이 구성하였다. PopupWindow의 역할에 따라 위 참고자료와 같이 spinner를 선언하기도 하고 ImageView를 보여주게 할 수도 있다.


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="50dp">

    <TextView
        android:id="@+id/popupquestion"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:padding="5dp"
        android:layout_margin="10dp"
        android:text="App.을 종료하겠습니까?"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="right"
        android:layout_below="@id/popupquestion">
    <Button
        android:id="@+id/yesbtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:padding="10dp"
        android:text="예"
        android:backgroundTint="@android:color/transparent" />

    <Button
        android:id="@+id/nobtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:layout_toRightOf="@id/yesbtn"
        android:text="아니오"
        android:backgroundTint="@android:color/transparent"/>
    </LinearLayout>
</RelativeLayout>

 

 3) MainActivity.java에 ImageButton Click시 PopupWindow 생성되도록 다음과 같이 설계한다. 보다보면 앞에서 ListView, Gallery 의 Adapter에서 getView() 할 때와 비슷하게 main이 되는 layout에 inflator로 추가 layout을 받아와서 동작하게 하는 방식임을 알 수 있다.

public class MainActivity extends AppCompatActivity {

    public ImageButton exampleImg;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // ImageButton 생성 및 연결. OnClickListener()로 Click시 PopupWindow를 생성하도록 함.
        exampleImg = (ImageButton)findViewById(R.id.exampleimg);
        exampleImg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                final PopupWindow popupWindow = new PopupWindow(v); // onClick(View v)로 받아온 View 위에 PopupWindow 생성
                // PopupWindow에 반영할 layout을 다음과 같이 생성 및 연결
                LayoutInflater layoutInflater = (LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE);
                View popup = layoutInflater.inflate(R.layout.layout_popup, null);
                popupWindow.setContentView(popup);
                popupWindow.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                popupWindow.setTouchable(true); // PopupWindow 위에서 Button의 Click이 가능하도록 setTouchable(true);
                Button yesBtn = (Button) popup.findViewById(R.id.yesbtn); // PopupWindow 상의 View의 Button 연결
                Button noBtn = (Button) popup.findViewById(R.id.nobtn);
                yesBtn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        finish(); // App.의 종료
                    }
                });
                noBtn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        popupWindow.dismiss(); // PopupWindow의 해제
                    }
                });
                popupWindow.showAsDropDown(v); // PopupWindow를 View 위에 뿌려 줌. 선언하지 않을 경우, PopupWindow가 보이지 않음
            }
        });
    }
}

(위 Code의 실행 결과)

    

 

[아니오] Button을 누를 경우

[예] Button을 누를 경우 

 

  이전에 Camera Intent 예제를 통해서 찍은 사진을 onActivityResult()를 이용하여 바로 확인하는 code의 기본 구조를 살펴 보았다. 이번에는 앞에서 구현한 기본 기능에다가 file.lists()를 더하여 특정 Directory 내 사진을 확인할 수 있는 Custom gallery 기능을 구현해보려 한다. 자료를 찾아보니 사진의 경로를 받아 Gallery 에 연결하는 방법이 있고 GridView에 연결하는 방법 등 여러가지가 있었는데, 오늘은 Gallery 기능을 활용해 보고자 한다.

1. 목적 : Gallery의 기본 구조를 살펴보고 Custom Gallery View를 구현해 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Envato tuts+ - Android Gallery View Tutorial (http://code.tutsplus.com/tutorials/android-sdk-displaying-images-with-an-enhanced-gallery--mobile-11130)
 2) Android People - Android Gallery, ImageView Example (http://www.androidpeople.com/android-gallery-imageview-example)

4. 과정
  이번 예시는 일전의 Camera Intent Example에 살을 붙여 사용했다.


 1) AndroidManifest.xml에 사진을 읽고 쓸 수 있도록 permission을 다음과 같이 추가한다.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <!-- 외부 저장소 read permission -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 외부 저장소 write permission -->

 

 2) activity_main.xml과 같이 App.에서 Gallery를 보여줄 곳에 다음과 같이 Gallery 변수를 추가한다.

 


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    android:orientation="vertical" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <!-- Camera Intent를 실행할 Button 선언 -->
    <Button
        android:id="@+id/takepicbtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:padding="5dp"
        android:text="Take Picture" />

    <!-- 사진의 경로를 받아다 String으로 보여줄 TextView 선언 -->
    <TextView
        android:id="@+id/imgpath"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/takepicbtn"
        android:padding="10dp"
        android:text="Hello World!"/>

    <!-- 지정한 경로 내의 사진들을 보여줄 Gallery 선언 -->
    <Gallery
        android:id="@+id/customgallery"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/imgpath"></Gallery>

    <!-- Gallery 내 선택한 사진을 크게 보여줄 ImageView 선언 -->
    <ImageView
        android:id="@+id/resultview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_below="@id/customgallery" />
</LinearLayout>

 

 3) Gallery의 Frame이 될 values/attrs.xml을 생성하여 다음과 같이 정의한다. 이 부분은 추후에 GalleryAdapter에서 적용된다.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="GalleryTheme">
        <attr name="android:galleryItemBackground"/>
    </declare-styleable>
</resources>

 

 4) main이 되는 activity에서 gallery를 연결할 Adapter를 다음과 같이 customizing하여 CustomGalleryAdapter.java와 같은 파일로 정의한다. CustomGalleryHolder를 통해 ImageView를 연결할 수도 있지만 기본 example처럼 context를 받아와 ImageView를 연결하여 사용할 예정이다.

public class CustomGalleryAdapter extends BaseAdapter {
    int CustomGalleryItemBg; // 앞서 정의해 둔 attrs.xml의 resource를 background로 받아올 변수 선언
    String mBasePath; // CustomGalleryAdapter를 선언할 때 지정 경로를 받아오기 위한 변수
    Context mContext; // CustomGalleryAdapter를 선언할 때 해당 activity의 context를 받아오기 위한 context 변수
    String[] mImgs; // 위 mBasePath내의 file list를 String 배열로 저장받을 변수
    Bitmap bm; // 지정 경로의 사진을 Bitmap으로 받아오기 위한 변수

    public String TAG = "Gallery Adapter Example :: ";

    public CustomGalleryAdapter(Context context, String basepath){ // CustomGalleryAdapter의 생성자
        this.mContext = context;
        this.mBasePath = basepath;

        File file = new File(mBasePath); // 지정 경로의 directory를 File 변수로 받아
        if(!file.exists()){
            if(!file.mkdirs()){
                Log.d(TAG, "failed to create directory");
            }
        }
        mImgs = file.list(); // file.list() method를 통해 directory 내 file 명들을 String[] 에 저장

        /* 앞서 정의한 attrs.xml에서 gallery array의 배경 style attribute를 받아옴 */
        TypedArray array = mContext.obtainStyledAttributes(R.styleable.GalleryTheme);
        CustomGalleryItemBg = array.getResourceId(R.styleable.GalleryTheme_android_galleryItemBackground, 0);
        array.recycle();
    }

    @Override
    public int getCount() { // Gallery array의 객체 갯수를 앞서 세어 둔 file.list()를 받은 String[]의 원소 갯수와 동일하다는 가정 하에 반환
        return mImgs.length;
    }

    @Override
    public Object getItem(int position) { // Gallery array의 해당 position을 반환
        return position;
    }

    @Override
    public long getItemId(int position) { // Gallery array의 해당 position을 long 값으로 반환
        return position;
    }


    // Override this method according to your need
    // 지정 경로 내 사진들을 보여주는 method.
   // Bitmap을 사용할 경우, memory 사용량이 커서 Thumbnail을 사용하거나 크기를 줄일 필요가 있음

   // setImageDrawable()이나 setImageURI() 등의 method로 대체 가능
    @Override
    public View getView(int index, View view, ViewGroup viewGroup)
    {
        // TODO Auto-generated method stub
      ImageView i = new ImageView(mContext); // Gallery array에 들어갈 ImageView 생성

        // 이 부분에서부터 'options.inJustDecodeBounds=false'까지는
        // Bitmap 사용시 나타나는 memory 부족 현상을 예방하기 위한 code. 경우에 따라서는 생략해도 가능하다.
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        int width = options.outWidth;
        int height = options.outHeight;
        int inSampleSize = 1;
        int reqWidth = 256;
        int reqHeight = 192;
        if((width > reqWidth) || (height > reqHeight)){
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        options.inSampleSize = inSampleSize;
        options.inJustDecodeBounds = false;


        bm = BitmapFactory.decodeFile(mBasePath+ File.separator +mImgs[index], options);
        Bitmap bm2 = ThumbnailUtils.extractThumbnail(bm, 300, 300); // 크기가 큰 원본에서 image를 300*300 thumnail을 추출.
                                                                                               // 이를 통해 view 가 까맣게 보이는 null 값을 피할 수 있었다.
        i.setLayoutParams(new Gallery.LayoutParams(300, 300));
        i.setImageBitmap(bm2);
        i.setVisibility(ImageView.VISIBLE);

        i.setBackgroundResource(CustomGalleryItemBg); // Gallery 원소의 BackGround를 생성자에서 지정한 값으로 지정
        i.setScaleType(ImageView.ScaleType.FIT_CENTER);

        if (bm != null && !bm.isRecycled()) {
            bm.recycle();
        }
        return i;
    }
}

 

 5) MainActivity.java와 같이 App. 에서 Gallery를 보일 부분에 다음과 같이 앞서 정의해 둔 Gallery와 Adapter를 선언하고 연결한다.


public class MainActivity extends AppCompatActivity {

    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
    public int inSampleSize = 1;

    private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
    private Uri fileUri;
    private static String basePath;

    public float imageViewRotation = 90;
    public String TAG = "Camera Example :: ";

    private Button takePicBtn;
    private ImageView resultView;
    private TextView imgPath;
    private Gallery customGallery;
    private CustomGalleryAdapter customGalAdapter;

    private String[] imgs;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

       // App.을 실행하자 마자 지정한 경로의 생성 및 접근에 용이하도록 아래와 같이 생성
        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "MyCameraApp");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.

        // Create the storage directory if it does not exist
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");
//                return null;
            }
        }

        basePath = mediaStorageDir.getPath();

        imgPath = (TextView)findViewById(R.id.imgpath);
        resultView = (ImageView)findViewById(R.id.resultview);
        takePicBtn = (Button)findViewById(R.id.takepicbtn);
        // Button click시, Camera Intent를 불러 옴 
        takePicBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // create Intent to take a picture and return control to the calling application
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

                fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image
                intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name

                // start the image capture Intent
                startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
            }
        });

        File file = new File(basePath);
        imgs = file.list();
        for(int i=0; i<imgs.length; i++){
            imgPath.setText(imgs[i]);
        }

        customGallery = (Gallery)findViewById(R.id.customgallery); // activity_main.xml에서 선언한 Gallery를 연결
        customGalAdapter = new CustomGalleryAdapter(getApplicationContext(), basePath); // 위 Gallery에 대한 Adapter를 선언
        customGallery.setAdapter(customGalAdapter); // Gallery에 위 Adapter를 연결
        // Gallery의 Item을 Click할 경우 ImageView에 보여주도록 함
        customGallery.setOnItemClickListener(new AdapterView.OnItemClickListener() { 
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Bitmap bm = BitmapFactory.decodeFile(basePath+ File.separator +imgs[position]);
                Bitmap bm2 = ThumbnailUtils.extractThumbnail(bm, bm.getWidth() / inSampleSize, bm.getHeight() / inSampleSize);
                resultView.setImageBitmap(bm2);
                imgPath.setText(basePath+File.separator+imgs[position]);
            }
        });
        // Gallery의 Item을 LongClick할 경우 해당 File을 삭제하도록 함.
      // 이 부분에서 동작은 하나, 삭제 후 결과가 View에 반영이 안되어 추가 보완 필요

        customGallery.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                File temp = new File(basePath+File.separator+imgs[position]);
                temp.delete();
                return false;
            }
        });
    }

 

(위 Code의 실행 결과)

  

  

 

 

 

  오늘 장이 끝나고 [네이버 금융]의 주요 뉴스란에 잠깐 대차거래에 주목한 투자 방법에 대한 기사가 메인으로 올라와 있었다.
  (http://finance.naver.com/news/news_read.nhn?mode=mainnews&office_id=018&article_id=0003414086)

 
[사진 1] 오늘자 Naver 금융의 '많이 본 뉴스' 카테고리

  뉴스 내용을 요약을 해보자면 12월은 배당이나 의결권 행사 등 여러 행사가 많아 주식을 빌려다 투자하는 투자자들 입장에서는 추가 비용 정산을 피하는 것이 유리하기 때문에 빌린 주식을 갚기 위하여 매수를 하게 되고, 그 과정 중에 해당 종목은 상승할 가능성이 많아 대차잔고가 많은 종목을 사는 것이 현 시점에서는 좋은 투자 방법이라는 것이다.

  없는 주식을 팔고 나중에 사다가 메꾼다? 단순히 주식을 자신이 가지고 있던 자본 내에서 사고 팔던 입장에서는 이게 뭔말인가 싶었다. 그러나 요즘 여기저기서 자료를 찾아보고 공부를 해보니 그리 먼 나라 이야기가 아닌, 개미인 나도 사용할 수 있는 제도였다.

  먼저 공매도와 대차거래에 대한 정의에 대하여 이해할 필요가 있었다. 공매도는 말그대로 '나에게 없는 주식을 파는(매도하는) 것'인데 보통 고점에서 먼저 매도를 하고 나중에 주가가 하락했을 때 사서 갚는 식으로 수익을 얻고자 할 때 사용하는 방법이라고 한다. 그리고 이 때 나에게 없는 주식을 팔기 위해서 대신 시장에 내놓을 주식이 필요한 데, 이 때 남에게서 주식을 빌려 거래를 하는 것 자체를 대차거래라고 한다. 이 내용은 매일 아침마다 대차거래 잔고를 알려주는 '인포스탁' 자료를 통해서 알 수 있었다.

 
[사진 2] 인포스탁 대차거래 관련 공지마다 달려있는 미주

  물론 이 과정에서는 남에게 주식을 빌려다 쓰는 것이니 이자가 생기기 마련이다. 그러나 정말 고점이 확실하다면 (위험하긴 하지만) 이런 방법을 쓰는 것도 투자자의 성향이나 숙련도에 따라서는 쓸만도 하리라는 생각이 들긴 했다. 하지만 나는 아직 심약 개미이기에 내가 직접 공매도를 하진 못하겠고 그냥 내가 가지고 있는 주식을 빌려주고 이자라도 받으면 좋겠다는 생각이 들었다. 그래서 거래하는 증권사에서 대차거래 서비스를 신청하니 그날 바로 내 주식을 대차거래해 갔다는 알림 문자가 이렇게 왔다. 아무래도 나만 몰랐지 이미 쓰고 있는 사람들이 많았구나 싶다.

 
[사진 3] 대차 거래 체결 내용 공지 문자

  막상 대차거래 체결 문자를 받으니 이자를 받겠다는 생각도 잠시, 곧 여기서 더 떨어지면 어쩌나라는 생각도 들기는 한다. 내 주식을 빌려서 공매도를 친 투자자도, 계속 가지고 있으려는 나도 모두 Win-Win하는 결과가 나왔으면 좋겠다.

+ Recent posts