Android 播放器通知欄樣式適配

一、獲取通知欄主題顏色

由於調用系統的屬性,獲取顏色在某些手機上是不兼容的。因此採用先創建一個系統通知欄對象,然後迭代其中的 View 獲取對應的顏色。代碼如下:

import android.app.Notification;
import android.content.Context;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by iOnesmile on 06/07/2017.
 */
public class NotificationColor {

    private static final String NOTIFICATION_TITLE = "notification_title";
    public static final int INVALID_COLOR = -1; // 無效顏色
    private static int notificationTitleColor = INVALID_COLOR; // 獲取到的顏色緩存

    /**
     * 獲取系統通知欄主標題顏色,根據Activity繼承自AppCompatActivity或FragmentActivity採取不同策略。
     *
     * @param context 上下文環境
     * @return 系統主標題顏色
     */
    public static int getNotificationColor(Context context) {
        try {
            if (notificationTitleColor == INVALID_COLOR) {
                if (context instanceof AppCompatActivity) {
                    notificationTitleColor = getNotificationColorCompat(context);
                } else {
                    notificationTitleColor = getNotificationColorInternal(context);
                }
            }
        } catch (Exception ignored) {
        }
        return notificationTitleColor;
    }

    /**
     * 通過一個空的Notification拿到Notification.contentView,通過{@link RemoteViews#apply(Context, ViewGroup)}方法返回通知欄消息根佈局實例。
     *
     * @param context 上下文
     * @return 系統主標題顏色
     */
    private static int getNotificationColorInternal(Context context) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder.setContentTitle(NOTIFICATION_TITLE);
        Notification notification = builder.build();
        try {
            ViewGroup root = (ViewGroup) notification.contentView.apply(context, new FrameLayout(context));
            TextView titleView = (TextView) root.findViewById(android.R.id.title);
            if (null == titleView) {
                iteratorView(root, new Filter() {
                    @Override
                    public void filter(View view) {
                        if (view instanceof TextView) {
                            TextView textView = (TextView) view;
                            if (NOTIFICATION_TITLE.equals(textView.getText().toString())) {
                                notificationTitleColor = textView.getCurrentTextColor();
                            }
                        }
                    }
                });
                return notificationTitleColor;
            } else {
                return titleView.getCurrentTextColor();
            }
        } catch (Exception e) {
            Log.e("NotificationColor", "", e);
            return getNotificationColorCompat(context);
        }
    }

    /**
     * 使用getNotificationColorInternal()方法,Activity不能繼承自AppCompatActivity(實測5.0以下機型可以,5.0及以上機型不行),
     * 大致的原因是默認通知佈局文件中的ImageView(largeIcon和smallIcon)被替換成了AppCompatImageView,
     * 而在5.0及以上系統中,AppCompatImageView的setBackgroundResource(int)未被標記爲RemotableViewMethod,導致apply時拋異常。
     *
     * @param context 上下文
     * @return 系統主標題顏色
     */
    private static int getNotificationColorCompat(Context context) {
        try {

            NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
            Notification notification = builder.build();
            int layoutId = notification.contentView.getLayoutId();
            ViewGroup root = (ViewGroup) LayoutInflater.from(context).inflate(layoutId, null);
            TextView titleView = (TextView) root.findViewById(android.R.id.title);
            if (null == titleView) {
                return getTitleColorIteratorCompat(root);
            } else {
                return titleView.getCurrentTextColor();
            }
        } catch (Exception e) {
        }
        return INVALID_COLOR;
    }

    private static void iteratorView(View view, Filter filter) {
        if (view == null || filter == null) {
            return;
        }
        filter.filter(view);
        if (view instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) view;
            for (int i = 0; i < viewGroup.getChildCount(); i++) {
                View child = viewGroup.getChildAt(i);
                iteratorView(child, filter);
            }
        }
    }

    private static int getTitleColorIteratorCompat(View view) {
        if (view == null) {
            return INVALID_COLOR;
        }
        List<TextView> textViews = getAllTextViews(view);
        int maxTextSizeIndex = findMaxTextSizeIndex(textViews);
        if (maxTextSizeIndex != Integer.MIN_VALUE) {
            return textViews.get(maxTextSizeIndex).getCurrentTextColor();
        }
        return INVALID_COLOR;
    }

    private static int findMaxTextSizeIndex(List<TextView> textViews) {
        float max = Integer.MIN_VALUE;
        int maxIndex = Integer.MIN_VALUE;
        int index = 0;
        for (TextView textView : textViews) {
            if (max < textView.getTextSize()) {
                // 找到字號最大的字體,默認把它設置爲主標題字號大小
                max = textView.getTextSize();
                maxIndex = index;
            }
            index++;
        }
        return maxIndex;
    }

    /**
     * 實現遍歷View樹中的TextView,返回包含TextView的集合。
     *
     * @param root 根節點
     * @return 包含TextView的集合
     */
    private static List<TextView> getAllTextViews(View root) {
        final List<TextView> textViews = new ArrayList<>();
        iteratorView(root, new Filter() {
            @Override
            public void filter(View view) {
                if (view instanceof TextView) {
                    textViews.add((TextView) view);
                }
            }
        });
        return textViews;
    }

    private interface Filter {
        void filter(View view);
    }
}

二、渲染播放圖標

播放器圖標的渲染,採用 v4 包中的 tint 方法,代碼如下:

public static Bitmap getBitmapByIdAndRender(Context context, int drawableResId, int renderColor) {
    Drawable drawable = getDrawable(context, drawableResId);
    drawable = tintDrawable(drawable, ColorStateList.valueOf(renderColor));
    return drawableToBitmap(drawable);
}

public static Drawable getDrawable(Context context, int imageRes) {
    Drawable drawable = context.getResources().getDrawable(imageRes);
    drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
    return drawable;
}

public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {
    final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
    DrawableCompat.setTintList(wrappedDrawable, colors);
    return wrappedDrawable;
}

在設置副標題的顏色時,我採用了給主標題設置一個透明度的方式來達到,通過 HSV 模型把顏色和透明度合成一個新的色值:

/**
 * 把RGB + Alpha合成爲一個新的RGB
 * @param alpha
 * @param color
 * @return
 */
public static int getCompoundColor(int alpha, int color) {
    float[] hsv = new float[]{0, 0, 1};
    Color.colorToHSV(color, hsv);
    hsv[2] = (alpha + 0.0f) / 0xFF;
    color = Color.HSVToColor(hsv);
    return color;
}

三、參考鏈接

Android自定義通知樣式適配 http://www.jianshu.com/p/426d85f34561

Android通知欄介紹與適配總結 http://iluhcm.com/2017/03/12/experience-of-adapting-to-android-notifications/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章