The PlaybackOverlayFragment of the sample app uses the PlaybackControlsGlue to set up playback controls based on the data model. This is the look when using the standard glue:
My problem is that I don't want the title/subtitle text to appear above the main player controls bar - we want them at the top left of the player screen instead. Therefore, to disable the showing of title/subtitle, I override createControlsRowAndPresenter() of the glue and use the empty-args constructor of PlaybackControlsRowPresenter instead:
#Override
public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
setControlsRow(controlsRow);
final View.OnKeyListener onKeyListener = this;
PlaybackControlsRowPresenter presenter = new PlaybackControlsRowPresenter() { // no AbstractDetailsDescriptionPresenter argument
#Override
protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
super.onBindRowViewHolder(vh, item);
vh.setOnKeyListener(onKeyListener);
}
#Override
protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
super.onUnbindRowViewHolder(vh);
vh.setOnKeyListener(null);
}
};
// secondaryActionsAdapter setup not shown
presenter.setOnActionClickedListener(new OnActionClickedListener() {
#Override
public void onActionClicked(Action action) {
dispatchAction(action);
}
});
return presenter;
}
The result? No title/subtitle show as expected but now there's more spacing between the primary controls bar and other rows:
What could I be doing wrong, or is it a bug with the leanback library?
Without those two rows of text, the playback controls are now at the top of that view. You can probably apply margins or padding to the playback controls to shift it to the expected location.
Turns out the playback controls need some view above it so they don't occupy the top of their container view (#Nick is right). But I wanted to share my solution in case anyone has a similar need.
PlaybackControlsRowPresenter can take in any presenter in its constructor, not just AbstractDetailsDescriptionPresenters. So createControlsRowAndPresenter() should look like this:
EmojiRowPresenter emojiRowPresenter = new EmojiRowPresenter() {
#Override
protected void onBindEmojiInfo(EmojiRowView rowView, EmojiInfo emojiInfo) {
rowView.setEmojiInfo(emojiInfo);
}
};
PlaybackControlsRowPresenter presenter = new PlaybackControlsRowPresenter(emojiRowPresenter) { // replace the default description presenter with custom presenter
...
}
// everything else stays as before
and EmojiRowPresenter is a subclass of Presenter that looks like this:
public abstract class EmojiRowPresenter extends Presenter {
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
EmojiRowView emojiRowView = new EmojiRowView(parent.getContext());
emojiRowView.setFocusable(true);
emojiRowView.setFocusableInTouchMode(true);
return new ViewHolder(emojiRowView);
}
#Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
EmojiRowView emojiRowView = (EmojiRowView) viewHolder.view;
PlaybackControlHelper glue = (PlaybackControlHelper) item;
EmojiInfo emojiInfo = glue.getEmojiInfo();
if (emojiInfo != null) {
onBindEmojiInfo(emojiRowView, emojiInfo);
}
}
#Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
// ...
}
protected abstract void onBindEmojiInfo(EmojiRowView rowView, EmojiInfo emojiInfo);
}
Of course, EmojiRowView creates the view from the layout that defines each item. Here's the end result:
Related
I would like to make my app accessible with Tlkback, but I don't know how could I set different content descriptions for both states of a switch or toggle button.
Do you have any suggestions?
You can set content description programmatically like this.
toggleButton.setOnClickListener( new View.OnClickListener() {
#Override
public void onClick( View v ) {
if( toggleButton.isChecked() ) {
toggleButton.setContentDescription( "Selected" );
} else {
toggleButton.setContentDescription( "Unselected" );
}
}
} );
Use AccessibilityNodeInfo's setChecked and setCheckable. https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo#setCheckable(boolean)
class MyAccessibilityDelegate extends View.AccessibilityDelegate {
MyButton button;
public constructor (MyButton button) {
this.button = button;
}
#Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setCheckable(true);
info.setChecked(button.getChecked());
And then add delegate to your view/layout/etc.
class MyButton extends View {
...
public constructor() {
setAccessibilityDelegate(new MyAccessibilityDelegate(this);
}
...
}
Explanation: All accessibility services should be using AccessibilityNodeInfo to determine what is spoken out loud. setCheckable makes the service aware that the View has modifiable states, while setChecked actually changes the "checked/not checked" callout.
There are several switches, which must be set same way:
private SwitchCompat switch1,switch2,...,switch10;
private void initSwitch(#NonNull SwitchCompat switchCompat) {
switchCompat.setOnCheckedChangeListener(this);
switchCompat.setTypeface(...);
}
How to pass switch id to initSwitch, so it would set all variables switch1, switch2, ..., switch10?
Because this will not work:
private void init(){
initSwitch(switch1, R.id.switch1)
initSwitch(switch1, R.id.switch2)
...
initSwitch(switch1, R.id.switch10)
}
private void initSwitch(#NonNull SwitchCompat switchCompat,int id) {
switchCompat.findById(id)
switchCompat.setOnCheckedChangeListener(this);
switchCompat.setTypeface(...);
}
As an answer to your question, I agree with Nilesh Rathod
But for this purpose a suggest you use something like ButterKnife
class ExampleActivity extends Activity {
#BindView(R.id.switch1) SwitchCompat switch1;
#BindView(R.id.switch2) SwitchCompat switch2;
#BindView(R.id.switch3) SwitchCompat switch3;
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
or if you want to listen on setOnCheckedChangeListener you don't need to define them
for example, we define onClickListener at the following code without any definition of views
#OnClick({R.id.switch1,R.id.switch2,R.id.switch3})
public void onClick(View view) {
switch(view.getId) {
case R.id.switch1: {
//do something here
break;
}
}
}
Try this way
Create a method like this
#SuppressWarnings("unchecked")
public <T extends View> T $(int id) {
return (T) findViewById(id);
}
Change your init() like this
switch1=$(R.id.switch1);
switch2=$(R.id.switch2);
switch10=$(R.id.switch10);
You can also use Butter Knife
Also other option is Data Binding
if you want to use kotlin then no need to findViewById()
You can use databinding.
Another way would be to traverse your layout like this and set the properties to your Switch.
LinearLayout layout = (LinearLayout)findViewById(R.id.root); // whatever layout you are using
setPropToSwitch(layout);
public void setPropToSwitch(LinearLayout layout) {
for (int i = 0; i < layout.getChildCount(); i++) {
View v = layout.getChildAt(i);
if (v instanceof SwitchCompat) {
//set properties
}
}
}
I didn't say it clearly, so this is my fault, but I can't add any libraries or use Kotlin because my great team won't let me.
It is simple, I just had "a window".
switch1= initSwitch(R.id.switch1);
switch2= initSwitch(R.id.switch2);
switch3= initSwitch(R.id.switch3);
....
switch10= initSwitch(R.id.switch10);
private SwitchCompat initSwitch(#IdRes int id) {
final SwitchCompat switchCompat = findViewById(id);
switchCompat.setOnCheckedChangeListener(this);
switchCompat.setTypeface(...);
return switchCompat;
}
Thank you all for your help.
I'm trying to implement a full screen mode with SimpleExoPlayerView. I've got this mostly working using setSystemUiVisibility.
During onCreate i add a OnSystemUiVisibilityChange listener to sync hiding the player controls with the actionbar.
#Override
public void onCreate(Bundle savedInstanceState) {
View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
(onSystemUiChange());
hideSystemUI();
}
In the OnSystemUiVisibilityChangeListener i'm also setting a timeout that matches the simpleExoplayerViews timeout so the controls and action bar are hidden at the same time.
#NonNull
private View.OnSystemUiVisibilityChangeListener onSystemUiChange() {
return new View.OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
mSimpleExoPlayerView.showController();
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
//sync the hide system ui with
//simpleExoPlayerView's auto hide timeout
hideSystemUI();
}
}, mSimpleExoPlayerView.getControllerShowTimeoutMs());
} else {
mSimpleExoPlayerView.hideController();
}
}
};
}
private void hideSystemUI() {
View rootView = findViewById(R.id.root);
rootView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
);
}
This works pretty well except in one case. If you tap the screen and then tap it again before the SimpleExoPlayerView controls timeout the SimpleExoPlayerView are hidden but the system ui do not get set until the timeout. Is there any events i can hook into instead?
I've tried setting a onClick and onTouch listener for my root layout but these events are not fired, i suspect SimpleExoPlayerView might be swallowing them?
ExoPlayer 2.10.4 has it.
exoplayer PlayerView has a method called
public void setControllerVisibilityListener(PlayerControlView.VisibilityListener listener) {
}
As of 2.6.1, SimpleExoPlayerView doesn't seem to have any visibility change listeners for the controls, but PlaybackControlView has. However, it's stored in a private field in SimpleExoPlayerView and there's no builtin way to a access it. To set your own listener, you'll either have to:
copy SimpleExoPlayerView.java to your project and make the required changes,
use reflection (don't forget to add proguard rules, if needed),
override exo_simple_player_view.xml and make sure it contains a PlaybackControlView, then find it using findViewById,
find it manually by traversing the view hierarchy.
In my opinion, the first and third options are the nicest, but the last one requires the least amount of changes, and it also works very well. Here is an example:
import com.google.android.exoplayer2.ui.PlaybackControlView;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
public SomeActivity extends Activity implements PlaybackControlView.VisibilityListener {
private initExoPlayer() {
// ...
addPlaybackControlVisibilityListener(mSimpleExoPlayerView, this);
}
#Override
public void onVisibilityChange(int visibility) {
// show/hide system ui here
}
private static void addPlaybackControlVisibilityListener(SimpleExoPlayerView playerView, PlaybackControlView.VisibilityListener listener) {
PlaybackControlView playbackControlView = findPlaybackControlView(playerView);
if (playbackControlView != null)
playbackControlView.setVisibilityListener(listener);
}
private static PlaybackControlView findPlaybackControlView(ViewGroup viewGroup) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
if (child instanceof PlaybackControlView)
return (PlaybackControlView) child;
if (child instanceof ViewGroup) {
PlaybackControlView result = findPlaybackControlView((ViewGroup) child);
if (result != null)
return result;
}
}
return null;
}
}
With Exoplayer 2.16.1 you can use setControllerVisibilityListener like this:
viewBinding.playerView.setControllerVisibilityListener { visibility ->
if (visibility == View.VISIBLE) {
// controller is visible
} else {
// controller is not visible
}
}
There are two classes we have 1. PlayerView 2. StyledPlayerView.
I am answering here for StyledPlayerView since PlayerView is deprecated now.
First create a class which extends StyledPlayerView and also your Class do implement this interface class CustomPlayerView extends StyledPlayerView implements StyledPlayerView.ControllerVisibilityListener So you need to override onVisibilityChanged Method:
#Override
public void onVisibilityChanged(int visibility) {
isControllerVisible = visibility == View.VISIBLE;
}
Now you can call this method on some other class where all your playerView methods present
binding.playerView.setControllerVisibilityListener(customPlayerView)
So on Visibility change of your controls you will get callbacks.
I have a Recyclerview, im animating a view inside individual list item, but when I scroll the recyclerview the animation is stopping. Its because recyclerview removes the items form its view so when we scroll back it fetches it back! But now i want that animation to keep going as I would stop it only when i get data from server!
All I want is the animation that I start in the individual items inside the recylerview shouldn't stop even if the recyclerview is scrolled and the view is out of focus and comes back to focus! I need to stop the animation in the code when I get the server data! I have the code where to stop the animation and it works if the item is not scrolled off the view!
btn.onClick -- this button is the onClick for the recyclerview list
item 1 btn.startAnimation(anim.xml) -- starting the animation
onSuccess -- server returns success btn.clearAnimation();
but before the onSuccess if we scroll the list the animation is stopped!
Please help!
By inspiring from crymson's answer i have made little easy and useful solution using tag method of View instead setting a boolean in complicated logic of your custom adapter.
#Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
super.onViewDetachedFromWindow(holder);
if (holder.getItemViewType() == TYPE_AD)
((ViewHolderForAd) holder).ivStory.setTag(false);
}
public class ViewHolderForAd extends RecyclerView.ViewHolder {
private ImageView ivStory;
TextView tvName;
public ViewHolderForAd(View view) {
super(view);
ivStory = (ImageView) view.findViewById(R.id.ivStoryImage);
tvName = (TextView) view.findViewById(R.id.tvAppName);
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
int pos = getAdapterPosition();
if (pos < 0) {
pos = (int) v.getTag();
}
customItemClickListener.onItemClicked(v, pos);
}
});
//ivStory.startAnimation(AnimationUtils.loadAnimation(context, R.anim.pulse_story));
ivStory.setTag(false); //Set default tag false to decrease risk of null
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
//...Your code...
if (!(boolean) holder1.ivStory.getTag()) {
holder1.ivStory.setTag(true);
holder1.ivStory.startAnimation(AnimationUtils.loadAnimation(context, R.anim.pulse_story));
}
//...Your code...//
}
You can use setTag(key, object) instead of setTag(object) if you already tagged something(like position) in your imageView.
Hope this helps someone.
Hard to give you a full solution but have you tried saving the animation state inside the ViewHolder that you are using? I'd recommend saving a boolean flag in the ViewHolder class you defined like isAnimating which is initially set to false and in your onBindViewHolder(...) method you can do something like
if (viewHolder.isAnimating) {
// start animation
} else {
// clear animation
}
viewHolder.btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewHolder.isAnimating = true;
// start animation
}
});
I'm looking in the documentation for some kind of an event that would allow me to detect when either a view is created or when the view is attached to the activity, anywhere in the view hierarchy, whether it's one level deep or multiple levels deep.
A method like this would be ideal, either at the Activity level, Window level, or Window.DecorView level:
void ViewAttachedToActivity(View view)
{
... //triggered each time an individual view is added to activity
}
The important part is that I want to be able to detect this event from the context of the Activity, not from the context of the child view itself.
Below is a rough demo of what I'm trying to accomplish. I'm wondering if a more efficient method exists:
P.S. I know I can accomplish the custom font part by subclassing all the text controls like TextView, EditText, Button, etc, and use them instead of the stock controls, but I'm looking for a simple workaround that might help me to avoid that.
(Please excuse the fact that this code is written in C# using Mono for Android, it should be simple to understand and mentally convert to Java)
public class BaseActivity : SherlockFragmentActivity
{
public Typeface Voltaire { get; set; }
bool pendingLayout = false;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Voltaire = Typeface.CreateFromAsset(Assets, "fonts/voltaire-regular.ttf");
Window.DecorView.ViewTreeObserver.GlobalLayout += new EventHandler(ViewTreeObserver_GlobalLayout);
Window.DecorView.ViewTreeObserver.PreDraw += new EventHandler<ViewTreeObserver.PreDrawEventArgs>(ViewTreeObserver_PreDraw);
}
void ViewTreeObserver_GlobalLayout(object sender, EventArgs e)
{
pendingLayout = true;
}
void ViewTreeObserver_PreDraw(object sender, ViewTreeObserver.PreDrawEventArgs e)
{
if (pendingLayout)
{
pendingLayout = false;
SetTypeFace(Window.DecorView, Voltaire);
}
}
public void SetTypeFace(View view, Typeface typeface)
{
if (view is TextView)
{
((TextView)view).Typeface = typeface;
}
if (view is ViewGroup)
{
ViewGroup viewgroup = (ViewGroup)view;
for (int i = 0; i < viewgroup.ChildCount; i++)
{
SetTypeFace(viewgroup.GetChildAt(i), typeface);
}
}
}
}
I dont think an event like this exists