Implemented a scalable control pad (#477)

* Refactored RemoteFragment and created a compound view for the
     actual remote. I called it ControlPad to make it more clear what
     its main function is.
   * Implemented a custom grid layout (SquareGridLayout) that will
     always be square. When its width and height are both set to
     match_parent, it will take the smallest of the two as the
     actual size.
   * For devices with a smallest width smaller then 360dp the ControlPad
     is sized to the maximum available space. For larger devices we still
     use the old fixed sizes.

Refactored code to use styles
This commit is contained in:
Martijn Brekhof 2017-10-27 20:24:23 +02:00 committed by Synced Synapse
parent bdefeafc3b
commit b764fc676c
14 changed files with 893 additions and 604 deletions

View File

@ -15,24 +15,14 @@
*/
package org.xbmc.kore.ui.sections.remote;
import android.annotation.TargetApi;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.BitmapFactory;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@ -56,8 +46,8 @@ import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.type.GlobalType;
import org.xbmc.kore.jsonrpc.type.ListType;
import org.xbmc.kore.jsonrpc.type.PlayerType;
import org.xbmc.kore.ui.widgets.ControlPad;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.RepeatListener;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
@ -74,7 +64,8 @@ import butterknife.Optional;
* Remote view
*/
public class RemoteFragment extends Fragment
implements HostConnectionObserver.PlayerEventsObserver {
implements HostConnectionObserver.PlayerEventsObserver,
ControlPad.OnPadButtonsListener {
private static final String TAG = LogUtils.makeLogTag(RemoteFragment.class);
/**
@ -102,9 +93,35 @@ public class RemoteFragment extends Fragment
*/
private String currentNowPlayingItemType = null;
private final Packet leftButtonPacket =
new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_LEFT, false, true,
true, (short)0, (byte)0);
private final Packet rightButtonPacket =
new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_RIGHT, false, true,
true, (short)0, (byte)0);
private final Packet upButtonPacket =
new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_UP, false, true,
true, (short)0, (byte)0);
private final Packet downButtonPacket =
new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_DOWN, false, true,
true, (short)0, (byte)0);
private final Packet selectButtonPack =
new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_SELECT, false, true,
true, (short)0, (byte)0);
private final ApiMethod<String> downButtonAction = new Input.Down();
private final ApiMethod<String> leftButtonAction = new Input.Left();
private final ApiMethod<String> upButtonAction = new Input.Up();
private final ApiMethod<String> rightButtonAction = new Input.Right();
private final ApiMethod<String> selectButtonAction = new Input.Select();
private final ApiMethod<String> backButtonAction = new Input.Back();
private final ApiMethod<String> infoButtonAction = new Input.ExecuteAction(Input.ExecuteAction.INFO);
private final ApiMethod<String> contextButtonAction = new Input.ExecuteAction(Input.ExecuteAction.CONTEXTMENU);
private final ApiMethod<String> osdButtonAction = new Input.ExecuteAction(Input.ExecuteAction.OSD);
@InjectView(R.id.info_panel) RelativeLayout infoPanel;
@InjectView(R.id.media_panel) RelativeLayout mediaPanel;
@InjectView(R.id.remote) RelativeLayout remotePanel;
@InjectView(R.id.remote) ControlPad controlPad;
@InjectView(R.id.info_title) TextView infoTitle;
@InjectView(R.id.info_message) TextView infoMessage;
@ -126,16 +143,6 @@ public class RemoteFragment extends Fragment
@Optional @InjectView(R.id.weather) ImageButton weatherButton;
@Optional @InjectView(R.id.system) ImageButton systemButton;
@InjectView(R.id.select) ImageView selectButton;
@InjectView(R.id.left) ImageView leftButton;
@InjectView(R.id.right) ImageView rightButton;
@InjectView(R.id.up) ImageView upButton;
@InjectView(R.id.down) ImageView downButton;
@InjectView(R.id.back) ImageView backButton;
@InjectView(R.id.info) ImageView infoButton;
@InjectView(R.id.context) ImageView contextButton;
@InjectView(R.id.osd) ImageView osdButton;
@InjectView(R.id.art) ImageView thumbnail;
@InjectView(R.id.title) TextView nowPlayingTitle;
@InjectView(R.id.details) TextView nowPlayingDetails;
@ -145,11 +152,6 @@ public class RemoteFragment extends Fragment
@InjectView(R.id.rewind) ImageButton rewindButton;
@InjectView(R.id.fast_forward) ImageButton fastForwardButton;
private Animation buttonInAnim;
private Animation buttonOutAnim;
// Touch listener that provides touch feedback
private View.OnTouchListener feedbackTouchListener;
// EventServer connection
private EventServerConnection eventServerConnection = null;
@ -162,27 +164,8 @@ public class RemoteFragment extends Fragment
hostManager = HostManager.getInstance(getActivity());
hostConnectionObserver = hostManager.getHostConnectionObserver();
buttonInAnim = AnimationUtils.loadAnimation(getActivity(), R.anim.button_in);
buttonOutAnim = AnimationUtils.loadAnimation(getActivity(), R.anim.button_out);
createEventServerConnection();
feedbackTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
buttonInAnim.setFillAfter(true);
v.startAnimation(buttonInAnim);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
v.startAnimation(buttonOutAnim);
break;
}
return false;
}
};
eventServerConnection = createEventServerConnection();
}
@Override
@ -190,43 +173,9 @@ public class RemoteFragment extends Fragment
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_remote, container, false);
ButterKnife.inject(this, root);
if (hostManager.getHostInfo().getUseEventServer() &&
(eventServerConnection != null)) {
// Setup d-pad to use EventServer
setupEventServerButton(leftButton, ButtonCodes.REMOTE_LEFT);
setupEventServerButton(rightButton, ButtonCodes.REMOTE_RIGHT);
setupEventServerButton(upButton, ButtonCodes.REMOTE_UP);
setupEventServerButton(downButton, ButtonCodes.REMOTE_DOWN);
controlPad.setOnPadButtonsListener(this);
setupEventServerButton(selectButton, ButtonCodes.REMOTE_SELECT);
} else {
// Otherwise, use json-rpc
setupRepeatButton(leftButton, new Input.Left());
setupRepeatButton(rightButton, new Input.Right());
setupRepeatButton(upButton, new Input.Up());
setupRepeatButton(downButton, new Input.Down());
setupDefaultButton(selectButton, new Input.Select(), null);
}
// Other buttons
setupDefaultButton(backButton, new Input.Back(), null);
setupDefaultButton(osdButton, new Input.ExecuteAction(Input.ExecuteAction.OSD), null);
setupDefaultButton(contextButton, new Input.ExecuteAction(Input.ExecuteAction.CONTEXTMENU), null);
// Info button, v17 uses a different window to display codec info so check version number
HostInfo hostInfo = hostManager.getHostInfo();
if (hostInfo.getKodiVersionMajor() < 17) {
setupDefaultButton(infoButton,
new Input.ExecuteAction(Input.ExecuteAction.INFO),
new Input.ExecuteAction(Input.ExecuteAction.CODECINFO));
} else {
setupDefaultButton(infoButton,
new Input.ExecuteAction(Input.ExecuteAction.INFO),
new Input.ExecuteAction(Input.ExecuteAction.PLAYERPROCESSINFO));
}
adjustRemoteButtons();
TypedArray styledAttributes = getActivity().getTheme().obtainStyledAttributes(new int[] {
R.attr.iconFastForward,
@ -253,53 +202,9 @@ public class RemoteFragment extends Fragment
buttons[i].setVisibility(shownItems.contains(String.valueOf(i)) ? View.VISIBLE : View.GONE);
}
// // Pad main content view to account for bottom system bar
// UIUtils.setPaddingForSystemBars(getActivity(), root, false, false, true);
// No clipping
// root.setClipToPadding(false);
return root;
}
@TargetApi(21)
private void adjustRemoteButtons() {
Resources.Theme theme = getActivity().getTheme();
TypedArray styledAttributes = theme.obtainStyledAttributes(new int[] {
R.attr.remoteButtonColorFilter,
R.attr.contentBackgroundColor});
// R.attr.remoteBackgroundColorFilter});
Resources resources = getResources();
int remoteButtonsColor = styledAttributes.getColor(styledAttributes.getIndex(0), resources.getColor(R.color.white)),
remoteBackgroundColor = styledAttributes.getColor(styledAttributes.getIndex(1), resources.getColor(R.color.dark_content_background_dim_70pct));
styledAttributes.recycle();
leftButton.setColorFilter(remoteButtonsColor);
rightButton.setColorFilter(remoteButtonsColor);
upButton.setColorFilter(remoteButtonsColor);
downButton.setColorFilter(remoteButtonsColor);
selectButton.setColorFilter(remoteButtonsColor);
backButton.setColorFilter(remoteButtonsColor);
infoButton.setColorFilter(remoteButtonsColor);
osdButton.setColorFilter(remoteButtonsColor);
contextButton.setColorFilter(remoteButtonsColor);
// On ICS the remote background isn't shown as the tinting isn't supported
//int backgroundResourceId = R.drawable.remote_background_square_black_alpha;
int backgroundResourceId = R.drawable.remote_background_square_black;
if (Utils.isLollipopOrLater()) {
remotePanel.setBackgroundTintList(ColorStateList.valueOf(remoteBackgroundColor));
remotePanel.setBackgroundResource(backgroundResourceId);
} else if (Utils.isJellybeanOrLater()) {
BitmapDrawable background = new BitmapDrawable(getResources(),
BitmapFactory.decodeResource(getResources(), backgroundResourceId));
background.setColorFilter(new PorterDuffColorFilter(remoteBackgroundColor, PorterDuff.Mode.SRC_IN));
remotePanel.setBackground(background);
}
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@ -311,7 +216,7 @@ public class RemoteFragment extends Fragment
super.onResume();
hostConnectionObserver.registerPlayerObserver(this, true);
if (eventServerConnection == null)
createEventServerConnection();
eventServerConnection = createEventServerConnection();
}
@Override
@ -324,105 +229,29 @@ public class RemoteFragment extends Fragment
}
}
private void createEventServerConnection() {
eventServerConnection = new EventServerConnection(
/**
* Creates a new EventServerConnection if using the event server
* is enabled in the preferences.
* @return EventServerConnection or null if usage is disabled
*/
private EventServerConnection createEventServerConnection() {
if (! hostManager.getHostInfo().getUseEventServer()) {
return null;
}
return new EventServerConnection(
hostManager.getHostInfo(),
new EventServerConnection.EventServerConnectionCallback() {
@Override
public void OnConnectResult(boolean success) {
if (!success) {
LogUtils.LOGD(TAG, "Couldnt setup EventServer, disabling it");
LogUtils.LOGD(TAG, "Couldn\'t setup EventServer, disabling it");
eventServerConnection = null;
}
}
});
}
private void setupRepeatButton(View button, final ApiMethod<String> action) {
button.setOnTouchListener(new RepeatListener(UIUtils.initialButtonRepeatInterval, UIUtils.buttonRepeatInterval,
new View.OnClickListener() {
@Override
public void onClick(View v) {
action.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
}, buttonInAnim, buttonOutAnim, getActivity().getApplicationContext()));
}
private void setupDefaultButton(View button,
final ApiMethod<String> clickAction,
final ApiMethod<String> longClickAction) {
// Set animation
button.setOnTouchListener(feedbackTouchListener);
if (clickAction != null) {
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
UIUtils.handleVibration(getActivity());
clickAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
});
}
if (longClickAction != null) {
button.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
UIUtils.handleVibration(getActivity());
longClickAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
return true;
}
});
}
}
private void setupEventServerButton(View button, final String action) {
final Packet packet =
new PacketBUTTON(ButtonCodes.MAP_REMOTE, action, false, true, true, (short)0, (byte)0);
button.setOnTouchListener(new RepeatListener(UIUtils.initialButtonRepeatInterval, UIUtils.buttonRepeatInterval,
new View.OnClickListener() {
@Override
public void onClick(View v) {
if (eventServerConnection != null)
eventServerConnection.sendPacket(packet);
}
}, buttonInAnim, buttonOutAnim, getActivity().getApplicationContext()));
}
// private void setupEventServerButton(View button, final String action) {
// short amount = 0;
// byte axis = 0;
// final Packet packetDown =
// new PacketBUTTON(ButtonCodes.MAP_REMOTE, action, true, true, false, amount, axis);
// final Packet packetUp =
// new PacketBUTTON(ButtonCodes.MAP_REMOTE, action, false, false, false, amount, axis);
//
// // Set animation and packet
// button.setOnTouchListener(new View.OnTouchListener() {
// @Override
// public boolean onTouch(View v, MotionEvent event) {
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
// buttonInAnim.setFillAfter(true);
// v.startAnimation(buttonInAnim);
// eventServerConnection.sendPacket(packetDown);
// break;
// case MotionEvent.ACTION_UP:
// case MotionEvent.ACTION_CANCEL:
// v.startAnimation(buttonOutAnim);
// eventServerConnection.sendPacket(packetUp);
// break;
// }
// return false;
// }
// });
// button.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// // Nothing to do
// }
// });
// }
/**
* Default callback for methods that don't return anything
*/
@ -731,11 +560,92 @@ public class RemoteFragment extends Fragment
}
if (showRemote) {
remotePanel.setVisibility(View.VISIBLE);
controlPad.setVisibility(View.VISIBLE);
buttonBarPanel.setVisibility(View.VISIBLE);
} else {
remotePanel.setVisibility(View.GONE);
controlPad.setVisibility(View.GONE);
buttonBarPanel.setVisibility(View.GONE);
}
}
@Override
public void leftButtonClicked() {
if (eventServerConnection != null) {
eventServerConnection.sendPacket(leftButtonPacket);
} else {
leftButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
}
@Override
public void rightButtonClicked() {
if (eventServerConnection != null) {
eventServerConnection.sendPacket(rightButtonPacket);
} else {
rightButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
}
@Override
public void upButtonClicked() {
if (eventServerConnection != null) {
eventServerConnection.sendPacket(upButtonPacket);
} else {
upButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
}
@Override
public void downButtonClicked() {
if (eventServerConnection != null) {
eventServerConnection.sendPacket(downButtonPacket);
} else {
downButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
}
@Override
public void selectButtonClicked() {
if (eventServerConnection != null) {
eventServerConnection.sendPacket(selectButtonPack);
} else {
selectButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
}
@Override
public void backButtonClicked() {
backButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
@Override
public void infoButtonClicked() {
infoButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
@Override
public boolean infoButtonLongClicked() {
ApiMethod<String> action;
HostInfo hostInfo = hostManager.getHostInfo();
// Info button, v17 uses a different window to display codec info so check version number
if (hostInfo.getKodiVersionMajor() < 17) {
action = new Input.ExecuteAction(Input.ExecuteAction.CODECINFO);
} else {
action = new Input.ExecuteAction(Input.ExecuteAction.PLAYERPROCESSINFO);
}
action.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
return true;
}
@Override
public void contextButtonClicked() {
contextButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
@Override
public void osdButtonClicked() {
osdButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler);
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.ui.viewgroups;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.RelativeLayout;
import org.xbmc.kore.R;
/**
* The square grid layout creates a square layout that will fit inside
* the boundaries provided by the parent layout. Note that all cells
* will have the same size.
*
* The attribute columnCount is available to specify the amount of columns
* when using SquareGridLayout in a XML layout file.
*/
public class SquareGridLayout extends ViewGroup {
private int columnCount = 1;
private int cellSize;
public SquareGridLayout(Context context) {
this(context, null);
}
public SquareGridLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SquareGridLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SquareGridLayout, 0, 0);
setColumnCount(a.getInt(R.styleable.SquareGridLayout_columnCount, 1));
a.recycle();
fixForRelativeLayout();
}
public void setColumnCount(int columnCount) {
if (columnCount < 1) throw new IllegalArgumentException("Column count must be 1 or more");
this.columnCount = columnCount;
}
/**
* Methods overridden to make sure we pass in the correct layout parameters for the child views
*/
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT,
MarginLayoutParams.WRAP_CONTENT);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int paddingWidth = getPaddingLeft() + getPaddingRight();
int paddingHeight = getPaddingTop() + getPaddingBottom();
int size;
int padding;
if ((width - paddingWidth) < (height - paddingHeight)) {
size = width;
padding = size - paddingWidth;
} else {
size = height;
padding = size - paddingHeight;
}
for (int y = 0; y < columnCount; y++) {
for (int x = 0; x < columnCount; x++) {
View child = getChildAt(y * size + x);
if (child != null) {
measureChildWithMargins(child,
MeasureSpec.makeMeasureSpec((padding + x) / columnCount, MeasureSpec.EXACTLY),
0,
MeasureSpec.makeMeasureSpec((padding + y) / columnCount, MeasureSpec.EXACTLY),
0);
}
}
}
setMeasuredDimension(size, size);
cellSize = padding;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// top left is used to position child views
left = getPaddingLeft();
top = getPaddingTop();
for (int y = 0; y < columnCount; y++) {
for (int x = 0; x < columnCount; x++) {
View child = getChildAt(y * columnCount + x);
MarginLayoutParams childLayoutParams = (MarginLayoutParams) child.getLayoutParams();
child.layout(left + (cellSize * x) / columnCount + childLayoutParams.leftMargin,
top + (cellSize * y) / columnCount + childLayoutParams.topMargin,
left + (cellSize * (x+1)) / columnCount - childLayoutParams.rightMargin,
top + (cellSize * (y+1)) / columnCount - childLayoutParams.bottomMargin
);
}
}
}
/**
* When used in a relative layout we need to set the layout parameters to
* the correct size manually. Otherwise the grid layout will be stretched.
*/
private void fixForRelativeLayout() {
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewGroup.LayoutParams pParams = getLayoutParams();
if (pParams instanceof RelativeLayout.LayoutParams) {
int size = Math.min(getWidth(), getHeight());
pParams.width = size;
pParams.height = size;
setLayoutParams(pParams);
}
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
}
}

View File

@ -0,0 +1,240 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.ui.widgets;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.BitmapFactory;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import org.xbmc.kore.R;
import org.xbmc.kore.ui.viewgroups.SquareGridLayout;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.RepeatListener;
import org.xbmc.kore.utils.Utils;
import butterknife.ButterKnife;
import butterknife.InjectView;
public class ControlPad extends SquareGridLayout
implements View.OnClickListener, View.OnLongClickListener {
private static final String TAG = LogUtils.makeLogTag(ControlPad.class);
private static final int initialButtonRepeatInterval = 400; // ms
private static final int buttonRepeatInterval = 80; // ms
public interface OnPadButtonsListener {
void leftButtonClicked();
void rightButtonClicked();
void upButtonClicked();
void downButtonClicked();
void selectButtonClicked();
void backButtonClicked();
void infoButtonClicked();
boolean infoButtonLongClicked();
void contextButtonClicked();
void osdButtonClicked();
}
private OnPadButtonsListener onPadButtonsListener;
@InjectView(R.id.select) ImageView selectButton;
@InjectView(R.id.left) ImageView leftButton;
@InjectView(R.id.right) ImageView rightButton;
@InjectView(R.id.up) ImageView upButton;
@InjectView(R.id.down) ImageView downButton;
@InjectView(R.id.back) ImageView backButton;
@InjectView(R.id.info) ImageView infoButton;
@InjectView(R.id.context) ImageView contextButton;
@InjectView(R.id.osd) ImageView osdButton;
public ControlPad(Context context) {
super(context);
initializeView(context);
}
public ControlPad(Context context, AttributeSet attrs) {
super(context, attrs);
initializeView(context);
}
public ControlPad(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initializeView(context);
}
@Override
public void setOnClickListener(@Nullable View.OnClickListener l) {
throw new Error("Use setOnPadButtonsListener(listener)");
}
@Override
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
throw new Error("Use setOnPadButtonsListener(listener)");
}
public void setOnPadButtonsListener(OnPadButtonsListener onPadButtonsListener) {
this.onPadButtonsListener = onPadButtonsListener;
}
private void initializeView(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.remote_control_pad, this);
ButterKnife.inject(this, this);
applyTheme();
setupListeners(context);
}
@Override
public void onClick(View v) {
if (onPadButtonsListener == null)
return;
switch (v.getId()) {
case R.id.select:
onPadButtonsListener.selectButtonClicked();
break;
case R.id.left:
onPadButtonsListener.leftButtonClicked();
break;
case R.id.right:
onPadButtonsListener.rightButtonClicked();
break;
case R.id.up:
onPadButtonsListener.upButtonClicked();
break;
case R.id.down:
onPadButtonsListener.downButtonClicked();
break;
case R.id.back:
onPadButtonsListener.backButtonClicked();
break;
case R.id.info:
onPadButtonsListener.infoButtonClicked();
break;
case R.id.context:
onPadButtonsListener.contextButtonClicked();
break;
case R.id.osd:
onPadButtonsListener.osdButtonClicked();
break;
default:
LogUtils.LOGD(TAG, "Unknown button "+v.getId()+" clicked");
}
}
@Override
public boolean onLongClick(View v) {
if ((onPadButtonsListener != null) && (v.getId() == R.id.info)) {
return onPadButtonsListener.infoButtonLongClicked();
}
return false;
}
@TargetApi(21)
private void applyTheme() {
Resources.Theme theme = getContext().getTheme();
TypedArray styledAttributes = theme.obtainStyledAttributes(new int[] {
R.attr.remoteButtonColorFilter,
R.attr.contentBackgroundColor});
Resources resources = getResources();
int remoteButtonsColor = styledAttributes.getColor(styledAttributes.getIndex(0), resources.getColor(R.color.white)),
remoteBackgroundColor = styledAttributes.getColor(styledAttributes.getIndex(1), resources.getColor(R.color.dark_content_background_dim_70pct));
styledAttributes.recycle();
leftButton.setColorFilter(remoteButtonsColor);
rightButton.setColorFilter(remoteButtonsColor);
upButton.setColorFilter(remoteButtonsColor);
downButton.setColorFilter(remoteButtonsColor);
selectButton.setColorFilter(remoteButtonsColor);
backButton.setColorFilter(remoteButtonsColor);
infoButton.setColorFilter(remoteButtonsColor);
osdButton.setColorFilter(remoteButtonsColor);
contextButton.setColorFilter(remoteButtonsColor);
// On ICS the remote background isn't shown as the tinting isn't supported
//int backgroundResourceId = R.drawable.remote_background_square_black_alpha;
int backgroundResourceId = R.drawable.remote_background_square_black;
if (Utils.isLollipopOrLater()) {
setBackgroundTintList(ColorStateList.valueOf(remoteBackgroundColor));
setBackgroundResource(backgroundResourceId);
} else if (Utils.isJellybeanOrLater()) {
BitmapDrawable background = new BitmapDrawable(getResources(),
BitmapFactory.decodeResource(getResources(), backgroundResourceId));
background.setColorFilter(new PorterDuffColorFilter(remoteBackgroundColor, PorterDuff.Mode.SRC_IN));
setBackground(background);
}
}
private void setupListeners(Context context) {
final Animation buttonInAnim = AnimationUtils.loadAnimation(context, R.anim.button_in);
final Animation buttonOutAnim = AnimationUtils.loadAnimation(context, R.anim.button_out);
RepeatListener repeatListener = new RepeatListener(initialButtonRepeatInterval,
buttonRepeatInterval, this,
buttonInAnim, buttonOutAnim, getContext());
OnTouchListener feedbackTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
buttonInAnim.setFillAfter(true);
v.startAnimation(buttonInAnim);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
v.startAnimation(buttonOutAnim);
break;
}
return false;
}
};
leftButton.setOnTouchListener(repeatListener);
rightButton.setOnTouchListener(repeatListener);
upButton.setOnTouchListener(repeatListener);
downButton.setOnTouchListener(repeatListener);
setupButton(selectButton, feedbackTouchListener);
setupButton(backButton, feedbackTouchListener);
setupButton(infoButton, feedbackTouchListener);
setupButton(contextButton, feedbackTouchListener);
setupButton(osdButton, feedbackTouchListener);
}
private void setupButton(View button, OnTouchListener feedbackTouchListener) {
button.setOnTouchListener(feedbackTouchListener);
button.setOnClickListener(this);
button.setOnLongClickListener(this);
}
}

View File

@ -15,218 +15,19 @@
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:baselineAligned="false">
<!-- For when nothing is playing or something not recognized is playing -->
<RelativeLayout
android:id="@+id/info_panel"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:paddingTop="@dimen/remote_page_indicator_height"
android:layout_marginLeft="@dimen/remote_content_hmargin"
android:layout_marginRight="@dimen/remote_content_hmargin"
tools:ignore="InconsistentLayout"
tools:visibility="gone">
<!--android:background="?attr/cardBackground">-->
<TextView
android:id="@+id/info_title"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
style="@style/TextAppearance.Info.Title"
tools:ignore="InconsistentLayout"/>
<TextView
android:id="@+id/info_message"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
style="@style/TextAppearance.Info.Details"
android:layout_below="@id/info_title"
tools:ignore="InconsistentLayout"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/media_panel"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/remote_content_hmargin"
android:layout_marginRight="@dimen/remote_content_hmargin">
<ImageView
android:id="@+id/art"
android:layout_width="@dimen/remote_poster_width"
android:layout_height="@dimen/remote_poster_height"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:scaleType="centerCrop"
android:contentDescription="@string/poster"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
style="@style/TextAppearance.Media.Remote.Title"
android:background="?attr/contentBackgroundColor"/>
<TextView
android:id="@+id/details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
style="@style/TextAppearance.Media.Remote.Details"
android:background="?attr/contentBackgroundColor"/>
<LinearLayout
android:id="@+id/media_button_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/default_icon_size"
android:layout_below="@id/details"
android:layout_alignLeft="@id/details"
android:layout_alignStart="@id/details"
android:layout_alignBottom="@+id/art"
android:orientation="horizontal"
style="@style/ButtonBar"
android:background="?attr/contentBackgroundColor">
<ImageButton
android:id="@+id/rewind"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconRewind"
android:contentDescription="@string/rewind"/>
<ImageButton
android:id="@+id/play"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconPlay"
android:contentDescription="@string/play"/>
<ImageButton
android:id="@+id/stop"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconStop"
android:contentDescription="@string/stop"/>
<ImageButton
android:id="@+id/fast_forward"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconFastForward"
android:contentDescription="@string/fast_forward"/>
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/remote"
android:layout_width="@dimen/remote_size"
android:layout_height="@dimen/remote_size"
android:layout_marginRight="@dimen/remote_content_hmargin"
android:layout_marginEnd="@dimen/remote_content_hmargin"
android:layout_gravity="center">
<ImageView
android:id="@+id/context"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:src="?attr/iconContext"
android:contentDescription="@string/codec_info"/>
<ImageView
android:id="@+id/up"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_toRightOf="@id/context"
android:layout_toEndOf="@id/context"
android:layout_alignTop="@id/context"
android:src="?attr/iconUp"
android:contentDescription="@string/up"/>
<ImageView
android:id="@+id/info"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_toRightOf="@id/up"
android:layout_toEndOf="@id/up"
android:layout_alignTop="@id/context"
android:src="?attr/iconInfo"
android:contentDescription="@string/info"/>
<ImageView
android:id="@+id/left"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_below="@id/context"
android:src="?attr/iconLeft"
android:contentDescription="@string/left"/>
<ImageView
android:id="@+id/select"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_toRightOf="@id/left"
android:layout_toEndOf="@id/left"
android:layout_alignTop="@id/left"
android:src="?attr/iconSelect"
android:contentDescription="@string/select"/>
<ImageView
android:id="@+id/right"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_toRightOf="@id/select"
android:layout_toEndOf="@id/select"
android:layout_alignTop="@id/left"
android:src="?attr/iconRight"
android:contentDescription="@string/right"/>
<ImageView
android:id="@+id/back"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_below="@id/left"
android:src="?attr/iconBack"
android:contentDescription="@string/back"/>
<ImageView
android:id="@+id/down"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_toRightOf="@id/back"
android:layout_toEndOf="@id/back"
android:layout_alignTop="@id/back"
android:src="?attr/iconDown"
android:contentDescription="@string/down"/>
<ImageView
android:id="@+id/osd"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_toRightOf="@id/down"
android:layout_toEndOf="@id/down"
android:layout_alignTop="@id/back"
android:src="?attr/iconMenu"
android:contentDescription="@string/osd"/>
</RelativeLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Right button bar -->
<LinearLayout
android:id="@+id/button_bar"
android:layout_width="@dimen/buttonbar_height"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:orientation="vertical"
style="@style/ButtonBar.Bottom">
<ImageButton
@ -329,4 +130,128 @@
android:tint="?attr/defaultButtonColorFilter"
android:contentDescription="@string/system"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@+id/button_bar"
android:layout_toStartOf="@+id/button_bar">
<!-- For when nothing is playing or something not recognized is playing -->
<RelativeLayout
android:id="@+id/info_panel"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:paddingTop="@dimen/remote_page_indicator_height"
android:layout_marginLeft="@dimen/remote_content_hmargin"
android:layout_marginRight="@dimen/remote_content_hmargin"
tools:ignore="InconsistentLayout"
tools:visibility="gone">
<!--android:background="?attr/cardBackground">-->
<TextView
android:id="@+id/info_title"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
style="@style/TextAppearance.Info.Title"
tools:ignore="InconsistentLayout"/>
<TextView
android:id="@+id/info_message"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
style="@style/TextAppearance.Info.Details"
android:layout_below="@id/info_title"
tools:ignore="InconsistentLayout"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/media_panel"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/remote_content_hmargin">
<ImageView
android:id="@+id/art"
android:layout_width="@dimen/remote_poster_width"
android:layout_height="@dimen/remote_poster_height"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:scaleType="centerCrop"
android:contentDescription="@string/poster"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
style="@style/TextAppearance.Media.Remote.Title"
android:background="?attr/contentBackgroundColor"/>
<TextView
android:id="@+id/details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
style="@style/TextAppearance.Media.Remote.Details"
android:background="?attr/contentBackgroundColor"/>
<LinearLayout
android:id="@+id/media_button_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/default_icon_size"
android:layout_below="@id/details"
android:layout_alignLeft="@id/details"
android:layout_alignStart="@id/details"
android:layout_alignBottom="@+id/art"
android:orientation="horizontal"
style="@style/ButtonBar"
android:background="?attr/contentBackgroundColor">
<ImageButton
android:id="@+id/rewind"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconRewind"
android:contentDescription="@string/rewind"/>
<ImageButton
android:id="@+id/play"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconPlay"
android:contentDescription="@string/play"/>
<ImageButton
android:id="@+id/stop"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconStop"
android:contentDescription="@string/stop"/>
<ImageButton
android:id="@+id/fast_forward"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconFastForward"
android:contentDescription="@string/fast_forward"/>
</LinearLayout>
</RelativeLayout>
<org.xbmc.kore.ui.widgets.ControlPad
android:id="@+id/remote"
style="@style/ControlPad"
app:columnCount="3"/>
</LinearLayout>
</RelativeLayout>

View File

@ -18,93 +18,105 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<include layout="@layout/remote_info_panel"/>
<RelativeLayout
android:id="@+id/media_panel"
<FrameLayout
android:id="@+id/top_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="@dimen/remote_top_panel_height"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="@dimen/remote_content_hmargin"
android:layout_marginRight="@dimen/remote_content_hmargin"
android:paddingTop="@dimen/remote_page_indicator_height">
android:layout_alignParentLeft="true">
<ImageView
android:id="@+id/art"
android:layout_width="@dimen/remote_poster_width"
android:layout_height="@dimen/remote_poster_height"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:scaleType="centerCrop"
android:contentDescription="@string/poster"/>
<include layout="@layout/remote_info_panel"/>
<LinearLayout
android:id="@+id/media_button_bar"
<RelativeLayout
android:id="@+id/media_panel"
android:layout_width="match_parent"
android:layout_height="@dimen/default_icon_size"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"
android:layout_alignBottom="@id/art"
android:orientation="horizontal"
style="@style/ButtonBar"
android:background="?attr/contentBackgroundColor">
<ImageButton
android:id="@+id/rewind"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconRewind"
android:contentDescription="@string/rewind"/>
<ImageButton
android:id="@+id/play"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconPlay"
android:contentDescription="@string/play"/>
<ImageButton
android:id="@+id/stop"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconStop"
android:contentDescription="@string/stop"/>
<ImageButton
android:id="@+id/fast_forward"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconFastForward"
android:contentDescription="@string/fast_forward"/>
</LinearLayout>
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/remote_content_hmargin"
android:layout_marginRight="@dimen/remote_content_hmargin"
android:paddingTop="@dimen/remote_page_indicator_height">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"
android:layout_alignTop="@id/art"
style="@style/TextAppearance.Media.Remote.Title"
android:background="?attr/contentBackgroundColor"/>
<TextView
android:id="@+id/details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/art"
android:layout_toEndOf="@id/art"
android:layout_below="@id/title"
android:layout_above="@id/media_button_bar"
style="@style/TextAppearance.Media.Remote.Details"
android:background="?attr/contentBackgroundColor"/>
</RelativeLayout>
<ImageView
android:id="@+id/art"
android:layout_width="@dimen/remote_poster_width"
android:layout_height="@dimen/remote_poster_height"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:contentDescription="@string/poster"
android:scaleType="centerCrop"/>
<LinearLayout
android:id="@+id/media_button_bar"
style="@style/ButtonBar"
android:layout_width="match_parent"
android:layout_height="@dimen/default_icon_size"
android:layout_alignBottom="@id/art"
android:layout_toEndOf="@id/art"
android:layout_toRightOf="@id/art"
android:background="?attr/contentBackgroundColor"
android:orientation="horizontal">
<ImageButton
android:id="@+id/rewind"
style="@style/Widget.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:contentDescription="@string/rewind"
android:src="?attr/iconRewind"/>
<ImageButton
android:id="@+id/play"
style="@style/Widget.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:contentDescription="@string/play"
android:src="?attr/iconPlay"/>
<ImageButton
android:id="@+id/stop"
style="@style/Widget.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:contentDescription="@string/stop"
android:src="?attr/iconStop"/>
<ImageButton
android:id="@+id/fast_forward"
style="@style/Widget.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:contentDescription="@string/fast_forward"
android:src="?attr/iconFastForward"/>
</LinearLayout>
<TextView
android:id="@+id/title"
style="@style/TextAppearance.Media.Remote.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignTop="@id/art"
android:layout_toEndOf="@id/art"
android:layout_toRightOf="@id/art"
android:background="?attr/contentBackgroundColor"/>
<TextView
android:id="@+id/details"
style="@style/TextAppearance.Media.Remote.Details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/media_button_bar"
android:layout_below="@id/title"
android:layout_toEndOf="@id/art"
android:layout_toRightOf="@id/art"
android:background="?attr/contentBackgroundColor"/>
</RelativeLayout>
</FrameLayout>
<!-- Bottom button bar -->
<LinearLayout
@ -215,91 +227,13 @@
android:contentDescription="@string/system"/>
</LinearLayout>
<RelativeLayout
android:id="@+id/remote"
android:layout_width="@dimen/remote_size"
android:layout_height="@dimen/remote_size"
<FrameLayout
style="@style/ControlPad.FrameLayout"
android:layout_above="@id/button_bar"
android:layout_marginBottom="@dimen/default_padding"
android:layout_centerHorizontal="true">
<ImageView
android:id="@+id/context"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:src="?attr/iconContext"
android:contentDescription="@string/codec_info"/>
<ImageView
android:id="@+id/up"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_toRightOf="@id/context"
android:layout_toEndOf="@id/context"
android:layout_alignTop="@id/context"
android:src="?attr/iconUp"
android:contentDescription="@string/up"/>
<ImageView
android:id="@+id/info"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_toRightOf="@id/up"
android:layout_toEndOf="@id/up"
android:layout_alignTop="@id/context"
android:src="?attr/iconInfo"
android:contentDescription="@string/info"/>
<ImageView
android:id="@+id/left"
android:layout_width="@dimen/remote_icon_size"
android:layout_height="@dimen/remote_icon_size"
android:layout_below="@id/context"
android:src="?attr/iconLeft"