Seize the day

POST : Android Dev Study

ShareActionProvider의 문제점과 자체 구현으로 해결하기

https://developer.android.com/reference/android/widget/ShareActionProvider 라는 좋은 기능이 있는데 아래 그림 처럼 최근에 선택한 앱을 우측에 표시해 줘서 매번 앱을 다시 선택하는 번거로움을 없애준다. 하지만 이것은 Android 12에서 치명적인 문제가 있는데 보안 때문에 폰에 어떤 앱이 설치되었있는지 조회할 수 없어서 메신저와 같은 주요 앱이 표시되지 않는다. 따라서 사실상 Android 12이상에서는 사용할 수 없는 기능이 되었다. 

 

따라서 이것을 자체적으로 구현했다.  버튼이 많고 타이틀에도 중요한 정보가 표시되어야 해서 요소간 간격을 줄일 필요가 있다. 

자체적으로 구현한 모습이다.

 

툴바를 커스터마이징을 해야해서 NoActionBar theme를 사용해야한다. 

values/styles.xml

    <style name="PhotoViewerActionButton" parent="Widget.AppCompat.ActionButton">
        <item name="android:minWidth">0dp</item>
        <item name="android:paddingStart">8dp</item>
        <item name="android:paddingEnd">8dp</item>
    </style>

    <style name="AppTheme.NoActionBar.PhotoViewer">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:actionButtonStyle">@style/PhotoViewerActionButton</item>
    </style>
    
    <style name="ToolbarPopupTheme" parent="@style/ThemeOverlay.AppCompat.Light" />

actionButtonStyle을 수정해서 메뉴 버튼의 간격도 더 좁게 조정했다. 

values-night/styles.xml 다크 모드일 때 색 대응

    <style name="ToolbarPopupTheme" parent="@style/ThemeOverlay.AppCompat.Dark" />

PhotoViewerActivity의 theme를 변경하기위해서 AndroidManifest.xml 수정

            android:theme="@style/AppTheme.NoActionBar.PhotoViewer">

 

layout/photo_viewer.xml 에 커스텀 툴바 추가

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutDirection="ltr"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:minHeight="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:contentInsetLeft="0dp"
        app:contentInsetStart="0dp"
        app:contentInsetStartWithNavigation="0dp"
        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ToolbarPopupTheme" />

contentInsetLeft 등을 조정해서 Up 버튼과 타이틀의 간격을 줄였다. 

Activity#onCreate에서 Toolbar 초기화

    private void setCustomToolbar() {
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

 

ShareActionProvider 자체구현하기

menu바에 올라가는 두 개의 버튼이 표시되는 layout/menu_viewer_share.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:clipToPadding="false"
    android:focusable="true">

    <ImageView
        android:id="@+id/share_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:tooltipText="@string/share_image"
        android:paddingStart="5dp"
        android:paddingEnd="5dp"
        android:paddingTop="8dp"
        android:paddingBottom="8dp"
        android:layout_gravity="center_vertical"
        android:src="@drawable/ic_share_variant_white_24dp" />

    <FrameLayout
        android:id="@+id/recent_share_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tooltipText="@string/share_image_using"
        android:background="?attr/selectableItemBackgroundBorderless"
        android:layout_gravity="center_vertical"
        android:visibility="gone"
        android:paddingStart="5dp"
        android:paddingEnd="5dp"
        android:paddingTop="8dp"
        android:paddingBottom="8dp">

        <ImageView
            android:id="@+id/recent_share_image"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:layout_gravity="center"
            android:src="@drawable/ic_export_variant_white_24dp" />
    </FrameLayout>
</LinearLayout>

배경 라운드 선을 표시할 drawbale/menu_viewer_share_bg.xml

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/transparent"/>
    <stroke android:width="1dp" android:color="#3fffffff" />
    <corners android:radius="4dp"/>
    <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

Activity의 menu/menu_viewer.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_item_share"
        app:actionLayout="@layout/menu_viewer_share"
        android:title="@string/share_image"
        app:showAsAction="always" />

PhotoViewerActivity 수정

    private MenuItem shareMenuItem;
    private View shareActionView;
    private View recentShareButton;
    private ImageView recentShareImageView;
    private String lastRecentShareData;
...


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_viewer, menu);
        shareMenuItem = menu.findItem(R.id.menu_item_share);
        shareActionView = shareMenuItem.getActionView();
        if (shareActionView != null) {
            shareActionView.findViewById(R.id.share_button).setOnClickListener(v -> onClickShare(null));
            recentShareButton = shareActionView.findViewById(R.id.recent_share_button);
            recentShareButton.setOnClickListener(v -> onClickRecentShare());
            recentShareImageView = shareActionView.findViewById(R.id.recent_share_image);
            updateRecentShareIcon();
        }
        return super.onCreateOptionsMenu(menu);
    }

    private void updateRecentShareIcon() {
        if (shareActionView == null || TextUtils.isEmpty(path)) {
            return;
        }
        String recentShareData = getRecentShareData();
        if (TextUtils.isEmpty(recentShareData)) {
            lastRecentShareData = null;
            shareActionView.setBackground(null);
            recentShareButton.setVisibility(View.GONE);
            return;
        }
        if (TextUtils.equals(lastRecentShareData, recentShareData)) {
            return;
        }

        lastRecentShareData = recentShareData;
        ComponentName componentName = ComponentName.unflattenFromString(recentShareData);
        if (componentName != null) {
            shareActionView.setBackgroundResource(R.drawable.menu_viewer_share_bg);
            recentShareButton.setVisibility(View.VISIBLE);
            Drawable icon = getActivityIcon(this, componentName.getPackageName(), componentName.getClassName());
            if (icon != null) {
                recentShareImageView.setImageDrawable(icon);
            } else {
                recentShareImageView.setImageResource(R.drawable.ic_export_variant_white_24dp);
            }
        } else {
            lastRecentShareData = null;
            shareActionView.setBackground(null);
            recentShareButton.setVisibility(View.GONE);
        }
    }

    @Nullable
    private Drawable getActivityIcon(Context context, String packageName, String activityName) {
        try {
            PackageManager packageManager = context.getPackageManager();
            Intent intent = new Intent();
            intent.setComponent(new ComponentName(packageName, activityName));
            ResolveInfo resolveInfo = packageManager.resolveActivity(intent, 0);
            return resolveInfo.loadIcon(packageManager);
        } catch (Exception ex) {
            return null;
        }
    }
    
    private String getRecentShareData() {
        if (isVideo()) {
            return Settings.recentShareVideoIntent.getValue();
        } else {
            return Settings.recentShareImageIntent.getValue();
        }
    }

updateRecentShareIcon()은 사진 혹은 Video 정보가 바뀔 때 마다 호출해 준다.

공유 버튼 누를 경우 

Intent intent = createShareIntent();
PendingIntent pendingIntent = PendingIntent.getBroadcast(
                            context,
                            0,
                            new Intent(context, IntentChooserReceiver.class).putExtra("isVideo", isVideo()),
                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
                    startActivity(Intent.createChooser(intent, context.getString(R.string.share_image_using), pendingIntent.getIntentSender()));

IntentChooserReceiver.kt 에서 선택한 앱 정보를 받는다

Settings.recentShareVideoIntent 와 Settings.recentShareImageIntent 정보 변경하기

class IntentChooserReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val clickedComponent: ComponentName = intent.getParcelableExtra(EXTRA_CHOSEN_COMPONENT) ?: return
        val isVideo = intent.getBooleanExtra("isVideo", false)
        if (isVideo)
            Settings.recentShareVideoIntent.value = clickedComponent.flattenToString()
        else
            Settings.recentShareImageIntent.value = clickedComponent.flattenToString()
    }
}

공유버튼을 눌러서 새로운 앱을 선택한 경우 바로 앱 아이콘을 바로 업데이트하기위해서

    @Override
    protected void onResume() {
        super.onResume();

        updateRecentShareIcon();
    }

최근 공유한 버튼을 누른 경우

 String data = getRecentShareData();
 if (!TextUtils.isEmpty(data)) {
     ComponentName componentName = ComponentName.unflattenFromString(data)
     try {
          Intent intent = createShareIntent();
          intent.setComponent(componentName); // target activity
          startActivity(intent);
     } catch (ActivityNotFoundException ex) {
          // Maybe the app uninstalled
          retry = true;
     }
}

 

AndroidManifest에 주요 앱을 작성해 주어야 한다. 그래야 아이콘이 제대로 표시되고 그 외는 기본 아이콘으로 표시된다.

    <queries>
        <package android:name="com.whatsapp" />
        <package android:name="com.facebook.orca" />
        <package android:name="com.skype.raider" />
        <package android:name="org.telegram.messenger" />
        <package android:name="com.snapchat.android" />
        <package android:name="org.thoughtcrime.securesms" />
        <package android:name="com.discord" />
        <package android:name="com.Slack" />
        <package android:name="com.viber.voip" />
        <package android:name="kik.android" />
        <package android:name="com.google.android.talk" />
        <package android:name="com.tencent.mm" />
        <package android:name="com.kakao.talk" />
        <package android:name="com.tencent.mobileqq" />
        <package android:name="jp.naver.line.android" />
    </queries>
</manifest>
top

posted at

2022. 11. 17. 22:46


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크