客户需求
要求自定义的音量提示,去除Android原生的,因为之前做过一段时间系统开发,所以这个问题就丢给我了。在Android TV上开发,调节音量类型AudioManager.STREAM_MUSIC,这个媒体音量,代码中暂时还没有看到其他音量类型,这样就简单了很多
这个需求分析以及务必要考虑到问题。
1.因为是IPTV开发,作为类似手机的Launcher进程,所以不需要担心进程杀死的情况
2.因为上面这点就考虑到两个方案,一个是在systemUI上去修改,另一个就是屏蔽systemUI系统音量,在IPTV中自定义一个系统级别的对话框显示(之前没有考虑到启动其他APP会遮挡对话框,所以这里改用系统级别对话框,显示在所有应用之上)
3.竖直Seekbar实现,并且自定义样式(因为控件不常用,属性都不熟,真是纠结了很久)
这里第二点提到的方案,我采用的是第二种,考虑到两个原因:framework层修改,看代码要修改很多布局、代码逻辑等等;调试方面也是不方便(在windows下开发,需要在服务器上编译,想想还真是麻烦)。
这里说说方案二的流程,之后想尝试实施方案一
方案二
屏蔽系统音量显示,SystemUI下volume_dialog.xml这个布局就是我们Android 7.1调节音量显示在最上方的Dialog。这里visibility属性设置为gone就很简单的实现了。
-
如何在IPTV应用中实现自定义VolumeDialog。
针对第二点做出详细的解决方案
IptvActivity.java
private void initVolumeDialog(){
mVolumeDialog = new AlertDialog.Builder(IptvActivity.mainactivity).create();
Window window = mVolumeDialog.getWindow();
mVolumeDialog.show();
window.setFlags(LayoutParams.FLAG_NOT_FOCUSABLE, LayoutParams.FLAG_NOT_FOCUSABLE);
//这里设置系统级提示框,因为怕在其他应用中调节音量导致对话框遮挡。
//在AndroidManifest.xml中申请权限android.permission.SYSTEM_ALERT_WINDOW"
window.setType(LayoutParams.TYPE_SYSTEM_ALERT);
window.clearFlags(LayoutParams.FLAG_DIM_BEHIND);
window.setContentView(R.layout.volume_dialog);
LayoutParams volumeDialogParams = window.getAttributes();
volumeDialogParams.gravity = Gravity.RIGHT|Gravity.CENTER_VERTICAL;
window.setBackgroundDrawableResource(android.R.color.transparent);
window.setAttributes(volumeDialogParams);
mVolumeLevel = (TextView) window.findViewById(R.id.tv_volume_level);
mVolumeSeekbar = (VerticalSeekbar) window.findViewById(R.id.volume_seekbar);
mVolumeSeekbar.setMax(15);
mVolumeDialog.dismiss();
}
public void showVolumeDialg(int progress){
mVolumeDialog.show();
mVolumeLevel.setText(progress + "");
mVolumeSeekbar.setProgress(progress);
}
静态注册音量改变广播
<receiver android:name=".broadcast.VolumeChangedReceiver">
<intent-filter>
<action android:name="android.media.VOLUME_CHANGED_ACTION"/>
</intent-filter>
</receiver>
VolumeChangedReceiver.java
public class VolumeChangedReceiver extends BroadcastReceiver{
private static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
private static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
@Override
public void onReceive(Context context, Intent intent) {
if (VOLUME_CHANGED_ACTION.equals(intent.getAction()) && (intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1) == AudioManager.STREAM_MUSIC)) {
if(IptvActivity.isMedianetStyle) {
AudioManager mAudioManager = (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
IptvActivity.mainactivity.showVolumeDialg(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
IptvActivity.mainactivity.handler.removeMessages(IptvActivity.MSG_HIDE_VOLUME_DIALOG);
IptvActivity.mainactivity.handler.sendEmptyMessageDelayed(IptvActivity.MSG_HIDE_VOLUME_DIALOG, 2000);
}
}
}
}
*附带竖直Seekbar的实现
public class VerticalSeekbar extends SeekBar {
public VerticalSeekbar(Context context) {
super(context);
}
public VerticalSeekbar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public VerticalSeekbar(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
protected void onDraw(Canvas c) {
c.rotate(-90);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
}
<com.justek.iptv.widget.VerticalSeekbar
android:layout_gravity="center_vertical"
android:id="@+id/volume_seekbar"
android:gravity="center"
android:layout_width="12dp"
android:layout_height="250dp"
android:thumb="@null"
android:progressDrawable="@drawable/seekbar_progress"
/>
seekbar_progress.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape>
<corners android:radius="5dip" />
<gradient
android:angle="90"
android:centerColor="#32000000"
android:endColor="#32000000"
android:startColor="#32000000"
android:type="linear" />
</shape>
</item>
<item>
<clip>
<shape>
<corners android:radius="5dip" />
<gradient
android:angle="90"
android:centerColor="#c8961e1e"
android:endColor="#c8961e1e"
android:startColor="#c8961e1e"
android:type="linear" />
</shape>
</clip>
</item>
</layer-list>
客户反馈
方案二导致了一个问题:当音量在最大(小)的时候,如果在Volume+(-),音量UI无法显示出来,原因很简单,这种方案修改只是监测音量变化广播,当音量没有变化时,则广播接收不到,则无法调用。
解决此问题思路:毫无疑问,广播接收总有它的来源,第一就是改变将它的发送条件,第二.添加一个自己应用接收的专属广播解决此问题。 第一种完全就是修改了Android原生音量条件,第二种显然就方便,不影响原生条件,应用层要什么构造什么
通过代码跟踪,在AudioService这个类的内部类VolumeStreamState种实现广播的发送
public boolean setIndex(int index, int device, String caller) {
...
//changed则为音量有改变
if (changed) {
...
// fire changed intents for all streams
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
//发送广播
sendBroadcastToAll(mVolumeChanged);
}
//自己增加广播发送的代码
mIptvVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mIptvVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
mIptvVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
sendBroadcastToAll(mIptvVolumeChanged);
return changed;
}
从自己增加的广播发送可知,setIndex,即使音量没有变化也会发送一个自己专属的广播
广播定义如下:
public class VolumeStreamState {
...
private final Intent mIptvVolumeChanged;
private VolumeStreamState(String settingName, int streamType) {
...
//action为自定义,自己在应用层广播接收过滤这个
mIptvVolumeChanged = new Intent("android.media.IPTV_VOLUME_CHANGED_ACTION");
mIptvVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
}
...
}