- 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 기능으로 리스트가 자연스럽게 갱신된다.
2019년 6월 7일 금요일
안드로이드 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에서 아이템을 가져와 클릭이벤트에 해당하는 아이템을 불러온다.
- 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에서 아이템을 가져와 클릭이벤트에 해당하는 아이템을 불러온다.
라벨:
안드로이드,
BaseAdapter
위치:
대한민국
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를 지정하고 다이얼로그 윈도우를 투명하게 변경한다.
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
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
라벨:
안드로이드,
FileProvider
위치:
대한민국
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
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
- 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는 액션바 메뉴에 표시.
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는 액션바 메뉴에 표시.
라벨:
안드로이드,
androidx,
ShareActionProvider
위치:
대한민국
안드로이드 화면녹화 예제 - MediaProjection
- 안드로이드 API 21 이상
- WRITE_EXTERNAL_STORAGE, RECORD_AUDIO 권한 필요.
1. 기본코드
- MediaRecorder
MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setOutputFile(videoFile);
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
mediaRecorder.setVideoSize(displayMetrics.widthPixels, displayMetrics.heightPixels);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setVideoEncodingBitRate(512 * 1000);
mediaRecorder.setVideoFrameRate(30);
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
- MediaProjection
final MediaRecorder screenRecorder = createRecorder();
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
MediaProjection.Callback callback = new MediaProjection.Callback() {
@Override
public void onStop() {
super.onStop();
if (screenRecorder != null) {
screenRecorder.stop();
screenRecorder.reset();
screenRecorder.release();
}
mediaProjection.unregisterCallback(this);
mediaProjection = null;
}
};
mediaProjection.registerCallback(callback, null);
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
mediaProjection.createVirtualDisplay(
"sample",
displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
screenRecorder.getSurface(), null, null);
screenRecorder.start();
> createVirtualDisplay 과정에서 MediaRecorder의 Surface를 가져와 버추얼 디스플레이로 녹화를 한다.
> 녹화를 위해서 오디오와 저장소 사용 권한을 허용해야 한다.
- 전체코드
public class MainActivity extends AppCompatActivity {
private static final String videoFile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/MediaProjection.mp4";
private static final int REQUEST_CODE_PERMISSIONS = 100;
private static final int REQUEST_CODE_MediaProjection = 101;
private MediaProjection mediaProjection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 퍼미션 확인
checkSelfPermission();
}
@Override
protected void onDestroy() {
// 녹화중이면 종료하기
if (mediaProjection != null) {
mediaProjection.stop();
}
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, final int resultCode, @Nullable final Intent data) {
// 미디어 프로젝션 응답
if (requestCode == REQUEST_CODE_MediaProjection && resultCode == RESULT_OK) {
screenRecorder(resultCode, data);
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* 음성녹음, 저장소 퍼미션
*
* @return
*/
public boolean checkSelfPermission() {
String temp = "";
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
temp += Manifest.permission.RECORD_AUDIO + " ";
}
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
temp += Manifest.permission.WRITE_EXTERNAL_STORAGE + " ";
}
if (TextUtils.isEmpty(temp) == false) {
ActivityCompat.requestPermissions(this, temp.trim().split(" "), REQUEST_CODE_PERMISSIONS);
return false;
} else {
initView();
return true;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_PERMISSIONS: {
int length = permissions.length;
for (int i = 0; i < length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
// 퍼미션 동의가 1개라도 부족하면 화면을 초기화 하지 않음
return;
}
initView();
}
return;
}
default:
return;
}
}
/**
* 뷰 초기화
*/
private void initView() {
findViewById(R.id.actionRec).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 미디어 프로젝션 요청
startMediaProjection();
}
});
}
/**
* 화면녹화
*
* @param resultCode
* @param data
*/
private void screenRecorder(int resultCode, @Nullable Intent data) {
final MediaRecorder screenRecorder = createRecorder();
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
MediaProjection.Callback callback = new MediaProjection.Callback() {
@Override
public void onStop() {
super.onStop();
if (screenRecorder != null) {
screenRecorder.stop();
screenRecorder.reset();
screenRecorder.release();
}
mediaProjection.unregisterCallback(this);
mediaProjection = null;
}
};
mediaProjection.registerCallback(callback, null);
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
mediaProjection.createVirtualDisplay(
"sample",
displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
screenRecorder.getSurface(), null, null);
final Button actionRec = findViewById(R.id.actionRec);
actionRec.setText("STOP REC");
actionRec.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
actionRec.setText("START REC");
if (mediaProjection != null) {
mediaProjection.stop();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(videoFile), "video/mp4");
startActivity(intent);
}
}
});
screenRecorder.start();
}
/**
* 미디어 프로젝션 요청
*/
private void startMediaProjection() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_CODE_MediaProjection);
}
}
/**
* 미디어 레코더
*
* @return
*/
private MediaRecorder createRecorder() {
MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setOutputFile(videoFile);
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
mediaRecorder.setVideoSize(displayMetrics.widthPixels, displayMetrics.heightPixels);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setVideoEncodingBitRate(512 * 1000);
mediaRecorder.setVideoFrameRate(30);
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
return mediaRecorder;
}
}
2. 샘플코드
소스코드
APK
- WRITE_EXTERNAL_STORAGE, RECORD_AUDIO 권한 필요.
1. 기본코드
- MediaRecorder
MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setOutputFile(videoFile);
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
mediaRecorder.setVideoSize(displayMetrics.widthPixels, displayMetrics.heightPixels);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setVideoEncodingBitRate(512 * 1000);
mediaRecorder.setVideoFrameRate(30);
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
- MediaProjection
final MediaRecorder screenRecorder = createRecorder();
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
MediaProjection.Callback callback = new MediaProjection.Callback() {
@Override
public void onStop() {
super.onStop();
if (screenRecorder != null) {
screenRecorder.stop();
screenRecorder.reset();
screenRecorder.release();
}
mediaProjection.unregisterCallback(this);
mediaProjection = null;
}
};
mediaProjection.registerCallback(callback, null);
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
mediaProjection.createVirtualDisplay(
"sample",
displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
screenRecorder.getSurface(), null, null);
screenRecorder.start();
> createVirtualDisplay 과정에서 MediaRecorder의 Surface를 가져와 버추얼 디스플레이로 녹화를 한다.
> 녹화를 위해서 오디오와 저장소 사용 권한을 허용해야 한다.
- 전체코드
public class MainActivity extends AppCompatActivity {
private static final String videoFile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/MediaProjection.mp4";
private static final int REQUEST_CODE_PERMISSIONS = 100;
private static final int REQUEST_CODE_MediaProjection = 101;
private MediaProjection mediaProjection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 퍼미션 확인
checkSelfPermission();
}
@Override
protected void onDestroy() {
// 녹화중이면 종료하기
if (mediaProjection != null) {
mediaProjection.stop();
}
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, final int resultCode, @Nullable final Intent data) {
// 미디어 프로젝션 응답
if (requestCode == REQUEST_CODE_MediaProjection && resultCode == RESULT_OK) {
screenRecorder(resultCode, data);
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* 음성녹음, 저장소 퍼미션
*
* @return
*/
public boolean checkSelfPermission() {
String temp = "";
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
temp += Manifest.permission.RECORD_AUDIO + " ";
}
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
temp += Manifest.permission.WRITE_EXTERNAL_STORAGE + " ";
}
if (TextUtils.isEmpty(temp) == false) {
ActivityCompat.requestPermissions(this, temp.trim().split(" "), REQUEST_CODE_PERMISSIONS);
return false;
} else {
initView();
return true;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_PERMISSIONS: {
int length = permissions.length;
for (int i = 0; i < length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
// 퍼미션 동의가 1개라도 부족하면 화면을 초기화 하지 않음
return;
}
initView();
}
return;
}
default:
return;
}
}
/**
* 뷰 초기화
*/
private void initView() {
findViewById(R.id.actionRec).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 미디어 프로젝션 요청
startMediaProjection();
}
});
}
/**
* 화면녹화
*
* @param resultCode
* @param data
*/
private void screenRecorder(int resultCode, @Nullable Intent data) {
final MediaRecorder screenRecorder = createRecorder();
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
MediaProjection.Callback callback = new MediaProjection.Callback() {
@Override
public void onStop() {
super.onStop();
if (screenRecorder != null) {
screenRecorder.stop();
screenRecorder.reset();
screenRecorder.release();
}
mediaProjection.unregisterCallback(this);
mediaProjection = null;
}
};
mediaProjection.registerCallback(callback, null);
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
mediaProjection.createVirtualDisplay(
"sample",
displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
screenRecorder.getSurface(), null, null);
final Button actionRec = findViewById(R.id.actionRec);
actionRec.setText("STOP REC");
actionRec.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
actionRec.setText("START REC");
if (mediaProjection != null) {
mediaProjection.stop();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(videoFile), "video/mp4");
startActivity(intent);
}
}
});
screenRecorder.start();
}
/**
* 미디어 프로젝션 요청
*/
private void startMediaProjection() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_CODE_MediaProjection);
}
}
/**
* 미디어 레코더
*
* @return
*/
private MediaRecorder createRecorder() {
MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setOutputFile(videoFile);
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
mediaRecorder.setVideoSize(displayMetrics.widthPixels, displayMetrics.heightPixels);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setVideoEncodingBitRate(512 * 1000);
mediaRecorder.setVideoFrameRate(30);
try {
mediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
return mediaRecorder;
}
}
2. 샘플코드
소스코드
APK
라벨:
안드로이드,
화면녹화,
MediaProjection
위치:
대한민국
2019년 3월 14일 목요일
ExoPlayer 예제
- 구글 ExoPlayer
https://github.com/google/ExoPlayer
https://developer.android.com/guide/topics/media/exoplayer
- 기본 미디어 플레이와 HLS 미디어 플레이 예제
1. 예제코드
- 어플리케이션 build.gradle 설정
implementation 'com.google.android.exoplayer:exoplayer:2.8.4'
- 기본코드
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private static class EventListener extends Player.DefaultEventListener {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
String stateString;
switch (playbackState) {
case Player.STATE_IDLE:
stateString = "ExoPlayer.STATE_IDLE";
break;
case Player.STATE_BUFFERING:
stateString = "ExoPlayer.STATE_BUFFERING";
break;
case Player.STATE_READY:
stateString = "ExoPlayer.STATE_READY";
break;
case Player.STATE_ENDED:
stateString = "ExoPlayer.STATE_ENDED";
break;
default:
stateString = "UNKNOWN_STATE";
break;
}
Log.d(TAG, "changed state to " + stateString + ", playWhenReady: " + playWhenReady);
}
}
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private SimpleExoPlayer exoPlayer;
private long playbackPosition;
private int currentWindow;
private boolean playWhenReady = true;
private EventListener eventListener = new EventListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
initPlayer();
}
@Override
protected void onPause() {
super.onPause();
releasePlayer();
}
@Override
protected void onStop() {
super.onStop();
releasePlayer();
}
private void initPlayer() {
String url = "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8";
PlayerView playerView = findViewById(R.id.playerView);
TrackSelection.Factory adaptiveTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
exoPlayer = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this), new DefaultTrackSelector(adaptiveTrackSelectionFactory), new DefaultLoadControl());
exoPlayer.addListener(new EventListener());
MediaSource mediaSource = url.endsWith(".m3u8") ? buildMediaSourceHLS(Uri.parse(url)) : buildMediaSourceVideo(Uri.parse(url));
exoPlayer.prepare(mediaSource, true, false);
exoPlayer.setPlayWhenReady(playWhenReady);
exoPlayer.seekTo(currentWindow, playbackPosition);
playerView.setPlayer(exoPlayer);
}
private void releasePlayer() {
if (exoPlayer != null) {
playbackPosition = exoPlayer.getCurrentPosition();
currentWindow = exoPlayer.getCurrentWindowIndex();
playWhenReady = exoPlayer.getPlayWhenReady();
exoPlayer.removeListener(eventListener);
exoPlayer.release();
exoPlayer = null;
}
}
private MediaSource buildMediaSourceHLS(Uri uri) {
String userAgent = System.getProperty("http.agent");
Log.d(TAG, "UserAgent : " + userAgent);
DataSource.Factory manifestDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(manifestDataSourceFactory).createMediaSource(uri);
return new ConcatenatingMediaSource(hlsMediaSource);
}
private MediaSource buildMediaSourceVideo(Uri uri) {
String userAgent = System.getProperty("http.agent");
Log.d(TAG, "UserAgent : " + userAgent);
ExtractorMediaSource videoSource =
new ExtractorMediaSource.Factory(
new DefaultHttpDataSourceFactory(TextUtils.isEmpty(userAgent) ? "DefaultHttpDataSourceFactory" : userAgent)).
createMediaSource(uri);
return new ConcatenatingMediaSource(videoSource);
}
}
> buildMediaSourceHLS 메소드는 HLS 미디어를 재생하기위한 메소드
> 예제에서는 PlayerView를 통해 ExoPlayer를 재생하는 예제.
> 안드로이드 MediaPlayer과 동일하게 구현 가능.
> 주요 특징은 별도 검색으로 확인해 볼 것.
> 사용된 HSL 미디어 소스
https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8
추가) 오디오와 영상이 분리된 경우
private MediaSource buildMediaSourceAdaptive(Uri video, Uri audio) {
ExtractorMediaSource videoSource =
new ExtractorMediaSource.Factory(
new DefaultHttpDataSourceFactory("exoplayer-codelab")).
createMediaSource(video);
ExtractorMediaSource audioSource =
new ExtractorMediaSource.Factory(
new DefaultHttpDataSourceFactory("exoplayer-codelab")).
createMediaSource(audio);
MergingMediaSource mergingMediaSource = new MergingMediaSource(videoSource, audioSource);
return new ConcatenatingMediaSource(mergingMediaSource);
}
2. 샘플코드
소스코드
APK
https://github.com/google/ExoPlayer
https://developer.android.com/guide/topics/media/exoplayer
- 기본 미디어 플레이와 HLS 미디어 플레이 예제
1. 예제코드
- 어플리케이션 build.gradle 설정
implementation 'com.google.android.exoplayer:exoplayer:2.8.4'
- 기본코드
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private static class EventListener extends Player.DefaultEventListener {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
String stateString;
switch (playbackState) {
case Player.STATE_IDLE:
stateString = "ExoPlayer.STATE_IDLE";
break;
case Player.STATE_BUFFERING:
stateString = "ExoPlayer.STATE_BUFFERING";
break;
case Player.STATE_READY:
stateString = "ExoPlayer.STATE_READY";
break;
case Player.STATE_ENDED:
stateString = "ExoPlayer.STATE_ENDED";
break;
default:
stateString = "UNKNOWN_STATE";
break;
}
Log.d(TAG, "changed state to " + stateString + ", playWhenReady: " + playWhenReady);
}
}
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private SimpleExoPlayer exoPlayer;
private long playbackPosition;
private int currentWindow;
private boolean playWhenReady = true;
private EventListener eventListener = new EventListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
initPlayer();
}
@Override
protected void onPause() {
super.onPause();
releasePlayer();
}
@Override
protected void onStop() {
super.onStop();
releasePlayer();
}
private void initPlayer() {
String url = "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8";
PlayerView playerView = findViewById(R.id.playerView);
TrackSelection.Factory adaptiveTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
exoPlayer = ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this), new DefaultTrackSelector(adaptiveTrackSelectionFactory), new DefaultLoadControl());
exoPlayer.addListener(new EventListener());
MediaSource mediaSource = url.endsWith(".m3u8") ? buildMediaSourceHLS(Uri.parse(url)) : buildMediaSourceVideo(Uri.parse(url));
exoPlayer.prepare(mediaSource, true, false);
exoPlayer.setPlayWhenReady(playWhenReady);
exoPlayer.seekTo(currentWindow, playbackPosition);
playerView.setPlayer(exoPlayer);
}
private void releasePlayer() {
if (exoPlayer != null) {
playbackPosition = exoPlayer.getCurrentPosition();
currentWindow = exoPlayer.getCurrentWindowIndex();
playWhenReady = exoPlayer.getPlayWhenReady();
exoPlayer.removeListener(eventListener);
exoPlayer.release();
exoPlayer = null;
}
}
private MediaSource buildMediaSourceHLS(Uri uri) {
String userAgent = System.getProperty("http.agent");
Log.d(TAG, "UserAgent : " + userAgent);
DataSource.Factory manifestDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
HlsMediaSource hlsMediaSource = new HlsMediaSource.Factory(manifestDataSourceFactory).createMediaSource(uri);
return new ConcatenatingMediaSource(hlsMediaSource);
}
private MediaSource buildMediaSourceVideo(Uri uri) {
String userAgent = System.getProperty("http.agent");
Log.d(TAG, "UserAgent : " + userAgent);
ExtractorMediaSource videoSource =
new ExtractorMediaSource.Factory(
new DefaultHttpDataSourceFactory(TextUtils.isEmpty(userAgent) ? "DefaultHttpDataSourceFactory" : userAgent)).
createMediaSource(uri);
return new ConcatenatingMediaSource(videoSource);
}
}
> buildMediaSourceHLS 메소드는 HLS 미디어를 재생하기위한 메소드
> 예제에서는 PlayerView를 통해 ExoPlayer를 재생하는 예제.
> 안드로이드 MediaPlayer과 동일하게 구현 가능.
> 주요 특징은 별도 검색으로 확인해 볼 것.
> 사용된 HSL 미디어 소스
https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8
추가) 오디오와 영상이 분리된 경우
private MediaSource buildMediaSourceAdaptive(Uri video, Uri audio) {
ExtractorMediaSource videoSource =
new ExtractorMediaSource.Factory(
new DefaultHttpDataSourceFactory("exoplayer-codelab")).
createMediaSource(video);
ExtractorMediaSource audioSource =
new ExtractorMediaSource.Factory(
new DefaultHttpDataSourceFactory("exoplayer-codelab")).
createMediaSource(audio);
MergingMediaSource mergingMediaSource = new MergingMediaSource(videoSource, audioSource);
return new ConcatenatingMediaSource(mergingMediaSource);
}
2. 샘플코드
소스코드
APK
VideoView 기본 예제
- VideoView의 기본 예제
- 플레이, 정지, 구간 스킵의 UI 요소들은 MediaController를 적용하는 예제
- 커스텀 UI로 구현할 경우 MediaController의 코드를 분석한 뒤 구현하는 것을 권장.
1. 기본 소스
- 로드 및 재생
String path = "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_10mb.mp4";
VideoView videoView = findViewById(R.id.videoView);
videoView.setVideoPath(path);
videoView.setMediaController(new MediaController(videoView.getContext()));
videoView.start();
> 기본 MediaController를 적용하면 현재포지션과 재생과 구간 스킵은 간편함.
> 커스텀 UI를 구현할 때 MediaController 소스를 참고해서 구현하는 것을 권장함.
- 포지션 이동
VideoView videoView = findViewById(R.id.videoView);
videoView.resume();
if (currentPosition > 0)
videoView.seekTo(currentPosition);
- 일지정지
VideoView videoView = findViewById(R.id.videoView);
if (videoView.canPause()) {
currentPosition = videoView.getCurrentPosition();
videoView.pause();
}
> VideoView는 resume될 때 현재 진행된 포지션을 유지하지 못한다.
> 실시간 스트리밍을 구현하는 경우가 아니면 currentPosition을 알고 있어야 한다.
- 정지
VideoView videoView = findViewById(R.id.videoView);
videoView.stopPlayback();
- Activity 예제
public class MainActivity extends AppCompatActivity {
private int currentPosition;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String path = "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_10mb.mp4";
VideoView videoView = findViewById(R.id.videoView);
videoView.setVideoPath(path);
videoView.setMediaController(new MediaController(videoView.getContext()));
videoView.start();
}
@Override
protected void onResume() {
super.onResume();
VideoView videoView = findViewById(R.id.videoView);
videoView.resume();
if (currentPosition > 0)
videoView.seekTo(currentPosition);
}
@Override
protected void onPause() {
VideoView videoView = findViewById(R.id.videoView);
if (videoView.canPause()) {
currentPosition = videoView.getCurrentPosition();
videoView.pause();
}
super.onPause();
}
@Override
protected void onDestroy() {
VideoView videoView = findViewById(R.id.videoView);
videoView.stopPlayback();
super.onDestroy();
}
}
> 사용된 MP4 소스 :
https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_10mb.mp4
2. 샘플코드
소스코드
APK
- 플레이, 정지, 구간 스킵의 UI 요소들은 MediaController를 적용하는 예제
- 커스텀 UI로 구현할 경우 MediaController의 코드를 분석한 뒤 구현하는 것을 권장.
1. 기본 소스
- 로드 및 재생
String path = "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_10mb.mp4";
VideoView videoView = findViewById(R.id.videoView);
videoView.setVideoPath(path);
videoView.setMediaController(new MediaController(videoView.getContext()));
videoView.start();
> 기본 MediaController를 적용하면 현재포지션과 재생과 구간 스킵은 간편함.
> 커스텀 UI를 구현할 때 MediaController 소스를 참고해서 구현하는 것을 권장함.
- 포지션 이동
VideoView videoView = findViewById(R.id.videoView);
videoView.resume();
if (currentPosition > 0)
videoView.seekTo(currentPosition);
- 일지정지
VideoView videoView = findViewById(R.id.videoView);
if (videoView.canPause()) {
currentPosition = videoView.getCurrentPosition();
videoView.pause();
}
> VideoView는 resume될 때 현재 진행된 포지션을 유지하지 못한다.
> 실시간 스트리밍을 구현하는 경우가 아니면 currentPosition을 알고 있어야 한다.
- 정지
VideoView videoView = findViewById(R.id.videoView);
videoView.stopPlayback();
- Activity 예제
public class MainActivity extends AppCompatActivity {
private int currentPosition;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String path = "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_10mb.mp4";
VideoView videoView = findViewById(R.id.videoView);
videoView.setVideoPath(path);
videoView.setMediaController(new MediaController(videoView.getContext()));
videoView.start();
}
@Override
protected void onResume() {
super.onResume();
VideoView videoView = findViewById(R.id.videoView);
videoView.resume();
if (currentPosition > 0)
videoView.seekTo(currentPosition);
}
@Override
protected void onPause() {
VideoView videoView = findViewById(R.id.videoView);
if (videoView.canPause()) {
currentPosition = videoView.getCurrentPosition();
videoView.pause();
}
super.onPause();
}
@Override
protected void onDestroy() {
VideoView videoView = findViewById(R.id.videoView);
videoView.stopPlayback();
super.onDestroy();
}
}
> 사용된 MP4 소스 :
https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_10mb.mp4
2. 샘플코드
소스코드
APK
2019년 3월 13일 수요일
안드로이드 데이터 바인딩 예제 - Android Jetpack
- 레퍼런스
https://developer.android.com/topic/libraries/data-binding?hl=ko
1. 데이터 바인딩
- gradle.properties 설정
android.databinding.enableV2=true
- xml 레이아웃
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="app.example.databindingapp.BuildConfig" />
<variable
name="listAdapter"
type="app.example.databindingapp.ListItemAdapter" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/packageName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@{BuildConfig.APPLICATION_ID}"
android:textColor="@android:color/black"
android:textSize="16dp" />
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/packageName"
android:adapter="@{listAdapter}" />
</RelativeLayout>
</layout>
> layout 안에 data와 실제 레이아웃 구성으로 나눠짐
> import와 variable 구분 할 것.
> 자세한 문법은 구글 레퍼런스 쪽에서 확인
> 바인딩을 사용하는 View는 id를 지정할 것.
- Activity
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
> setContentView 대신 DataBindingUtil의 setContentView를 사용한다.
> xml 레이아웃에서 데이터 바인딩을 정의하면 generatedJava 쪽에 코드가 자동 생성된다.
> 생성이 안되는 경우 Rebuild Project로 자동 생성을 시도하면 된다.
2. ListView를 데이터 바인딩으로 구성하기
- row_text_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="java.lang.String" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@{item}"
android:textColor="@android:color/black"
android:textSize="16dp" />
</RelativeLayout>
</layout>
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<import type="app.example.databindingapp.BuildConfig" />
<variable
name="listAdapter"
type="app.example.databindingapp.ListItemAdapter" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/packageName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@{BuildConfig.APPLICATION_ID}"
android:textColor="@android:color/black"
android:textSize="16dp" />
<TextView
android:id="@+id/versionName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:padding="16dp"
android:text="@{BuildConfig.VERSION_NAME}"
android:textColor="@android:color/black"
android:textSize="16dp" />
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/mode"
android:layout_below="@id/packageName"
android:adapter="@{listAdapter}" />
<TextView
android:id="@+id/mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@android:color/black"
android:gravity="center"
android:padding="16dp"
android:text="DEBUG APP"
android:textColor="@android:color/white"
android:textSize="16dp"
android:visibility="@{BuildConfig.DEBUG ? View.VISIBLE : View.GONE}" />
</RelativeLayout>
</layout>
- MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Android");
ListItemAdapter listItemAdapter = new ListItemAdapter(getBaseContext(), list);
activityMainBinding.setListAdapter(listItemAdapter);
}
}
> 데이터 바인딩으로 처리하면 전체 코드는 매우 간결해진다.
> 데이터 바인딩을 적절하게 사용하면 편리하지만 단순히 코드의 양을 줄이기 위한 용도로 사용한다면 부작용이 발생한다.
3. 샘플코드
소스코드
APK
https://developer.android.com/topic/libraries/data-binding?hl=ko
1. 데이터 바인딩
- gradle.properties 설정
android.databinding.enableV2=true
- xml 레이아웃
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="app.example.databindingapp.BuildConfig" />
<variable
name="listAdapter"
type="app.example.databindingapp.ListItemAdapter" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/packageName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@{BuildConfig.APPLICATION_ID}"
android:textColor="@android:color/black"
android:textSize="16dp" />
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/packageName"
android:adapter="@{listAdapter}" />
</RelativeLayout>
</layout>
> layout 안에 data와 실제 레이아웃 구성으로 나눠짐
> import와 variable 구분 할 것.
> 자세한 문법은 구글 레퍼런스 쪽에서 확인
> 바인딩을 사용하는 View는 id를 지정할 것.
- Activity
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
> setContentView 대신 DataBindingUtil의 setContentView를 사용한다.
> xml 레이아웃에서 데이터 바인딩을 정의하면 generatedJava 쪽에 코드가 자동 생성된다.
> 생성이 안되는 경우 Rebuild Project로 자동 생성을 시도하면 된다.
2. ListView를 데이터 바인딩으로 구성하기
- row_text_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="java.lang.String" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@{item}"
android:textColor="@android:color/black"
android:textSize="16dp" />
</RelativeLayout>
</layout>
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<import type="app.example.databindingapp.BuildConfig" />
<variable
name="listAdapter"
type="app.example.databindingapp.ListItemAdapter" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/packageName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@{BuildConfig.APPLICATION_ID}"
android:textColor="@android:color/black"
android:textSize="16dp" />
<TextView
android:id="@+id/versionName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:padding="16dp"
android:text="@{BuildConfig.VERSION_NAME}"
android:textColor="@android:color/black"
android:textSize="16dp" />
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/mode"
android:layout_below="@id/packageName"
android:adapter="@{listAdapter}" />
<TextView
android:id="@+id/mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@android:color/black"
android:gravity="center"
android:padding="16dp"
android:text="DEBUG APP"
android:textColor="@android:color/white"
android:textSize="16dp"
android:visibility="@{BuildConfig.DEBUG ? View.VISIBLE : View.GONE}" />
</RelativeLayout>
</layout>
- MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Android");
ListItemAdapter listItemAdapter = new ListItemAdapter(getBaseContext(), list);
activityMainBinding.setListAdapter(listItemAdapter);
}
}
> 데이터 바인딩으로 처리하면 전체 코드는 매우 간결해진다.
> 데이터 바인딩을 적절하게 사용하면 편리하지만 단순히 코드의 양을 줄이기 위한 용도로 사용한다면 부작용이 발생한다.
3. 샘플코드
소스코드
APK
라벨:
데이터 바인딩,
안드로이드,
Android Jetpack,
Data Binding
위치:
대한민국
피드 구독하기:
글 (Atom)