Skip to content

Instantly share code, notes, and snippets.

@rharter
Last active March 1, 2019 09:29
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save rharter/c2787f9ddd32651e8885 to your computer and use it in GitHub Desktop.
Save rharter/c2787f9ddd32651e8885 to your computer and use it in GitHub Desktop.
<resources>
<declare-styleable name="ThemeableMediaRouteButton">
<!-- This drawable is a state list where the "checked" state
indicates active media routing. Checkable indicates connecting
and non-checked / non-checkable indicates
that media is playing to the local device only. -->
<attr name="routeEnabledDrawable" format="reference" />
<attr name="iconColor" format="reference|color" />
<attr name="android:minWidth" />
<attr name="android:minHeight" />
</declare-styleable>
</resources>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" >
<item
android:id="@+id/menu_cast_item"
android:icon="@drawable/ic_chrome_off"
android:orderInCategory="0"
app:showAsAction="always"
app:actionProviderClass="com.ryanharter.mediaroute.widgets.ThemeableMediaRouteActionProvider"
android:title="@string/menu_cast"/>
</menu>
<resources>
<style name="Widget.MediaRouter.MediaRouteButton" parent="Widget.MediaRouter.Light.MediaRouteButton">
<item name="routeEnabledDrawable">@drawable/ic_chrome_media_route</item>
<item name="iconColor">@color/red</item>
</style>
<style name="Widget.MediaRouter.MediaRouteButton.Section1">
<item name="iconColor">@color/white</item>
</style>
<style name="Widget.MediaRouter.MediaRouteButton.Section2">
<item name="iconColor">@color/blue</item>
</style>
</resources>
package com.ryanharter.mediaroute.widgets;
import android.content.Context;
import android.support.v7.app.MediaRouteActionProvider;
import android.support.v7.app.MediaRouteButton;
/**
* A MediaRouteActionProvider that allows the use of a ThemeableMediaRouteButton.
*/
public class ThemeableMediaRouteActionProvider extends MediaRouteActionProvider {
public ThemeableMediaRouteActionProvider(Context context) {
super(context);
}
@Override public MediaRouteButton onCreateMediaRouteButton() {
return new ThemeableMediaRouteButton(getContext());
}
}
package com.ryanharter.mediaroute.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.app.MediaRouteButton;
import android.util.AttributeSet;
import com.ryanharter.mediaroute.R;
/**
* Allows theming of the MediaRouteButton using the theme associated with
* the context passed in.
*/
public class ThemeableMediaRouteButton extends MediaRouteButton {
private static final String TAG = ThemeableMediaRouteButton.class.getSimpleName();
private int mMinWidth;
private int mMinHeight;
private int mColor;
private Drawable mRemoteIndicator;
public ThemeableMediaRouteButton(Context context) {
this(context, null);
}
public ThemeableMediaRouteButton(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.mediaRouteButtonStyle);
}
public ThemeableMediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ThemeableMediaRouteButton, defStyleAttr, 0);
mColor = a.getColor(R.styleable.ThemeableMediaRouteButton_iconColor, 0);
setRemoteIndicatorDrawable(a.getDrawable(
R.styleable.ThemeableMediaRouteButton_routeEnabledDrawable));
mMinWidth = a.getDimensionPixelSize(
R.styleable.ThemeableMediaRouteButton_android_minWidth, 0);
mMinHeight = a.getDimensionPixelSize(
R.styleable.ThemeableMediaRouteButton_android_minHeight, 0);
a.recycle();
}
@Override protected void drawableStateChanged() {
super.drawableStateChanged();
if (mRemoteIndicator != null) {
int[] myDrawableState = getDrawableState();
mRemoteIndicator.setState(myDrawableState);
invalidate();
}
}
private void setRemoteIndicatorDrawable(Drawable d) {
if (mRemoteIndicator != null) {
mRemoteIndicator.setCallback(null);
unscheduleDrawable(mRemoteIndicator);
}
mRemoteIndicator = d;
if (d != null) {
d.setColorFilter(mColor, PorterDuff.Mode.SRC_ATOP);
d.setCallback(this);
d.setState(getDrawableState());
d.setVisible(getVisibility() == VISIBLE, false);
}
refreshDrawableState();
}
@Override protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mRemoteIndicator;
}
@Override public void jumpDrawablesToCurrentState() {
// We can't call super to handle the background so we do it ourselves.
//super.jumpDrawablesToCurrentState();
if (getBackground() != null) {
DrawableCompat.jumpToCurrentState(getBackground());
}
// Handle our own remote indicator.
if (mRemoteIndicator != null) {
DrawableCompat.jumpToCurrentState(mRemoteIndicator);
}
}
@Override public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (mRemoteIndicator != null) {
mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false);
}
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int minWidth = Math.max(mMinWidth,
mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicWidth() : 0);
final int minHeight = Math.max(mMinHeight,
mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicHeight() : 0);
int width;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = Math.min(widthSize, minWidth + getPaddingLeft() + getPaddingRight());
break;
default:
case MeasureSpec.UNSPECIFIED:
width = minWidth + getPaddingLeft() + getPaddingRight();
break;
}
int height;
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = Math.min(heightSize, minHeight + getPaddingTop() + getPaddingBottom());
break;
default:
case MeasureSpec.UNSPECIFIED:
height = minHeight + getPaddingTop() + getPaddingBottom();
break;
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
if (mRemoteIndicator != null) {
final int left = getPaddingLeft();
final int right = getWidth() - getPaddingRight();
final int top = getPaddingTop();
final int bottom = getHeight() - getPaddingBottom();
final int drawWidth = mRemoteIndicator.getIntrinsicWidth();
final int drawHeight = mRemoteIndicator.getIntrinsicHeight();
final int drawLeft = left + (right - left - drawWidth) / 2;
final int drawTop = top + (bottom - top - drawHeight) / 2;
mRemoteIndicator.setBounds(drawLeft, drawTop,
drawLeft + drawWidth, drawTop + drawHeight);
mRemoteIndicator.draw(canvas);
}
}
}
<resources>
<style name="Theme.MyApp">
<item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton</item>
</style>
<style name="Theme.MyApp.Section1">
<item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton.Section1</item>
</style>
<style name="Theme.MyApp.Section2">
<item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton.Section2</item>
</style>
</resources>
@ivamacio
Copy link

Hi Harter, do you mind publishing a sample? Thanks.

@FrancoisBlavoet
Copy link

I have just implemented the same requirement, it seems that the cast lib has evolved in the right direction since your gist, setRemoteIndicatorDrawable() is now a public method.
So all that is left is just to call it in the overrided constructor, no need to recopy half the class anymore :)

@billyjoker
Copy link

I've tried step by step... it does not work

@AbdulRehmanNazar
Copy link

I have done it. you are awesome man ^-^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment