2019년 6월 7일 금요일

안드로이드 RecyclerView 예제

- RecyclerView.ViewHolder 적용
- androidx.recyclerview.widget.DiffUtil 적용


1. RecyclerView.Adapter 구현
public class TextRecyclerAdapter extends RecyclerView.Adapter<TextRecyclerAdapter.Holder> {
    private LayoutInflater inflater;
    private final ArrayList<String> items = new ArrayList<>();

    public void updateList(final List<String> newItems) {
        TextDiff callback = new TextDiff(this.items, newItems);
        DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback);
        this.items.clear();
        this.items.addAll(newItems);
        result.dispatchUpdatesTo(this);
    }

    @NonNull
    @Override
    public Holder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (inflater == null)
            inflater = LayoutInflater.from(parent.getContext());

        View itemView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
        return new Holder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull Holder holder, int position) {
        holder.bindData(getItem(position));

    }

    public String getItem(int position) {
        return items != null && position < getItemCount() ? items.get(position) : null;
    }

    @Override
    public int getItemCount() {
        return items == null ? 0 : items.size();
    }

    static class Holder extends RecyclerView.ViewHolder {
        private final TextView text;

        public Holder(@NonNull View itemView) {
            super(itemView);
            this.text = itemView.findViewById(android.R.id.text1);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Object tag = v.getTag();
                    if (tag instanceof String) {

                    }
                }
            });
        }

        public void bindData(String item) {
            itemView.setTag(item);
            if (item == null)
                return;
            text.setText(item);
        }
    }

    static class TextDiff extends DiffUtil.Callback {
        private final List<String> oldItems;
        private final List<String> newItems;

        public TextDiff(List<String> oldItems, List<String> newItems) {
            this.oldItems = oldItems;
            this.newItems = newItems;
        }

        @Override
        public int getOldListSize() {
            return oldItems.size();
        }

        @Override
        public int getNewListSize() {
            return newItems.size();
        }

        @Override
        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
            if (oldItemPosition > newItemPosition)
                return false;
            return oldItems.get(oldItemPosition).equals(newItems.get(oldItemPosition));
        }

        @Override
        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
            if (oldItemPosition > newItemPosition)
                return false;
            return oldItems.get(oldItemPosition).equals(newItems.get(oldItemPosition));
        }
    }
}


- ViewHolder 클래스를 구현해서 사용한다.
- DiffUtil 클래스를 구현해서 리스트 갱신을 효율적으로 구현한다.






2. RecyclerView 구현
public class RecyclerListFragment extends Fragment {

    public RecyclerListFragment() {
        // Required empty public constructor
    }

    public static RecyclerListFragment newInstance() {
        RecyclerListFragment fragment = new RecyclerListFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_recycler_list, container, false);
        RecyclerView recyclerView = view.findViewById(R.id.recyclerView);
        recyclerView.setHasFixedSize(true);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(recyclerView.getContext());
        linearLayoutManager.setOrientation(RecyclerView.VERTICAL);
        TextRecyclerAdapter textRecyclerAdapter = new TextRecyclerAdapter();
        recyclerView.setAdapter(textRecyclerAdapter);

        ArrayList<String> items = new ArrayList<>();
        items.add("Text Item 1");
        items.add("Text Item 2");
        items.add("Text Item 3");
        textRecyclerAdapter.updateList(items);

        return view;
    }
}


- updateList를 호출하면 DiffUtil 기능으로 리스트가 자연스럽게 갱신된다.

안드로이드 BaseAdapter 예제

- android.widget.BaseAdapter 적용
- ViewHolder 방식 사용



1. BaseAdapter 구현
public class TextListAdapter extends BaseAdapter {
    private LayoutInflater inflater;

    static class Holder {

        private final TextView text;

        public Holder(View itemView) {
            this.text = itemView.findViewById(android.R.id.text1);
        }

        void bindData(String item) {
            text.setText(item);
        }
    }

    private final ArrayList<String> contentItems = new ArrayList<>();

    @Override
    public int getCount() {
        return contentItems == null ? 0 : contentItems.size();
    }

    @Override
    public String getItem(int position) {
        return contentItems != null && position < getCount() ? contentItems.get(position) : null;
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (inflater == null)
            inflater = LayoutInflater.from(parent.getContext());

        Holder holder;
        if (convertView == null) {
            convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
            holder = new Holder(convertView);
        } else {
            holder = (Holder) convertView.getTag();
        }

        holder.bindData(getItem(position));
        return convertView;
    }
}

- Holder 클래스(ViewHolder)를 View 태그에 넣어서 재활용한다.
- ListView 어뎁터 구현의 가장 기본이 되는 방식.




2. ListView 구현
public class TextListFragment extends Fragment {

    public TextListFragment() {
        // Required empty public constructor
    }

    public static TextListFragment newInstance() {
        TextListFragment fragment = new TextListFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ArrayList<String> items = new ArrayList<>();
        items.add("Text Item 1");
        items.add("Text Item 2");
        items.add("Text Item 3");

        View view = inflater.inflate(R.layout.fragment_text_list, container, false);
        ListView listView = view.findViewById(R.id.listView);
        listView.setAdapter(new TextListAdapter(items));
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Object item = parent.getItemAtPosition(position);
                if (item instanceof String) {

                }
            }
        });
        return view;
    }
}


- AdapterView에서 아이템을 가져와 클릭이벤트에 해당하는 아이템을 불러온다.



2019년 5월 23일 목요일

안드로이드 커스텀 다이얼로그 예제

- 기본코드
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);

View itemView = activity.getLayoutInflater().inflate(R.layout.dialog_view, null);

itemView.findViewById(R.id.actionClose).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setView(itemView);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));

dialog.show();



다이얼로그를 생성한뒤 setView를 한다.
다이얼로그의 FEATURE_NO_TITLE를 지정하고 다이얼로그 윈도우를 투명하게 변경한다.

2019년 5월 15일 수요일

안드로이드 FileProvider 예제

- FileProvider를 통해 앱 내부 저장소에 저장된 파일을 외부로 공유한다.


1. 기본코드
- res/xml/file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path
        name="image"
        path="temp_image/" />

    <files-path
        name="text"
        path="temp_text/" />
</paths>


image, text 이름은 실제로 temp_image, temp_text 디렉토리명으로 연결된다.

files-path를 적용했으므로 디렉토리 경로는
/data/user/0/simple.app.simplefileprovider/files/temp_image/
/data/user/0/simple.app.simplefileprovider/files/temp_text/



- AndroidManifest.xml
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="simple.app.simplefileprovider.fileprovider"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
</application>

android:authorities 값은 패키지명에 fileprovider를 붙이는게 일반적이다.



- Intent 파일 Uri 전달.
File dir = new File(getFilesDir(), "temp_image");
File file = new File(dir, "image.jpg");

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(FileProvider.getUriForFile(getBaseContext(), AUTHORITY, file), "image/*");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);

FileProvider를 사용해서 Uri를 가져온다.
여기서 사용되는 Uri주소는
content://simple.app.simplefileprovider.fileprovider/image/image.jpg

FLAG_GRANT_READ_URI_PERMISSION를 사용해야 한다.
안드로이드 N 버전 부터 반드시 적용해야 하는 사양이다.


2. 샘플코드
소스코드
APK





2019년 5월 14일 화요일

안드로이드 Fragment Tab 예제

- Fragment를 활용한 탭 예제


0. 탭 화면을 구성할 때 주의사항.
네비게이션 흐름상 ListFragment에서 ListItemFragment로 이동해야 하는 경우

getSupportFragmentManager().beginTransaction().replace(R.id.tab_container, ListItemFragment.newInstance()).commit();

형태로 사용하는 경우가 종종 있지만

Tab 내에서 UI 이동이 발생하면 Fragment 흐름을 관리하기 어렵다.
Tab을 사용할 때는 Activity로 List 항목을 표현하는 것을 추천.



1. 탭 구성
Tab1Fragment, Tab2Fragment, Tab3Fragment를 생성한 뒤

FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.findFragmentById(R.id.tab_container) == null) {
Tab1Fragment tab1 = Tab1Fragment.newInstance("Home Tab");
Tab2Fragment tab2 = Tab2Fragment.newInstance("Dash Board Tab");
Tab3Fragment tab3 = Tab3Fragment.newInstance("Notification Tab");
fragmentManager.beginTransaction()
.add(R.id.tab_container, tab1, tab1.getClass().getSimpleName()).show(tab1)
.add(R.id.tab_container, tab2, tab2.getClass().getSimpleName()).hide(tab2)
.add(R.id.tab_container, tab3, tab3.getClass().getSimpleName()).hide(tab3).commit();
}

첫번째 탭만 show를 하고 나머지 탭은 모두 hide 처리
Activity의 onCreate에서 위 코드를 적용하면 FragmentManager에 적용된 순서와 동일하게
onCreate > onCreateView > onResume
상태가 된다.





2. Tab 이동
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction beginTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment item : fragments) {
if (item.getClass().getSimpleName().equals(tabName)) {
beginTransaction.show(item);
} else {
beginTransaction.hide(item);
}
}
beginTransaction.commit();

getFragments 메소드를 통해서 현재 탭을 모두 가져와서 보여질 Fragment만 show 처리하고 나머지 Fragment는 모두 hide 처리한다.




3. 탭 데이터 갱신
동일한 탭 또는 다른탭을 선택 했을 때 Fragment 내용을 변경해야 한다면 인터페이스를 적용해서 데이터를 갱신한다.

- 인터페이스
public interface OnDataUpdate {

    /**
     * 데이터 갱신
     */
    void onUpdateData();
}


- 인터페이스 구현
public class Tab1Fragment extends Fragment implements OnDataUpdate {
    
    @Override
    public void onUpdateData() {
        Log.d("Fragment", text + " : onUpdateData");
        count++;
        TextView textView = getView().findViewById(R.id.text);
        textView.setText(String.format("%s %d", text, count));
    }
}


- 데이터 갱신
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction beginTransaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment item : fragments) {
if (item.getClass().getSimpleName().equals(tabName)) {
beginTransaction.show(item);

/*보여질 탭 갱신*/
if (item instanceof OnDataUpdate)
((OnDataUpdate) item).onUpdateData();

} else {
beginTransaction.hide(item);
}
}
beginTransaction.commit();

OnDataUpdate 인터페이스를 구현한 Fragment를 instanceof로 체크한 다음 onUpdateData를 호출해서 show가 되는 Fragment를 갱신한다.




4. 샘플코드
소스코드
APK

  



2019년 3월 21일 목요일

안드로이드 QR 코드 리더 예제

- 오픈소스 라이브러리를 이용한 예제
- ZXing 공식 페이지
https://github.com/zxing/zxing




1. 기본코드
- 어플리케이션 build.gradle
implementation 'com.journeyapps:zxing-android-embedded:3.6.0'


- AndroidManifest.xml
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="fullSensor"
tools:replace="android:screenOrientation" />

> tools:replace="android:screenOrientation"을 추가하지 않으면 android:screenOrientation="fullSensor" 에러가 발생한다.


- Activity
public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();

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

        findViewById(R.id.action).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new IntentIntegrator(MainActivity.this).initiateScan();
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (result != null) {
            if (result.getContents() != null) {
                String text = result.getContents();

                if (TextUtils.isEmpty(text) == false) {
                    TextView textView = findViewById(R.id.text);
                    textView.setText(text);
                }
                Log.d(TAG, "QR : " + result.getContents());
            }
            return;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
}

> initiateScan(); 메소드를 호출하면 CaptureActivity가 호출되고 카메라 사용 퍼미션에 대한 안내가 표시된다.



2. 샘플코드
소스코드
APK








- QR





2019년 3월 18일 월요일

ShareActionProvider 예제 - androidx

- 기본코드
MenuItem ic_menu_share = menu.add("share").setIcon(android.R.drawable.ic_menu_share);
shareActionProvider = new ShareActionProvider(this);
ic_menu_share.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
MenuItemCompat.setActionProvider(ic_menu_share, shareActionProvider);

- createChooser 호출
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("http://www.google.com"));

shareActionProvider.setShareIntent(intent);
startActivity(Intent.createChooser(intent, "share"));


> createChooser를 호출하면 하단에 선택 메뉴가 표시.
> setShareIntent는 액션바 메뉴에 표시.