Android dataBinding - How to get resource id of a View in xml - android

In Android Studio my data binding itself works and is set up fine. I have a boolean defined like this:
<resources>
<bool name="showAds">false</bool>
</resources>
and in a layout.xml file i would like to referenced this boolean (which works fine) but i want to assign a id based on this boolean. Let me show you what i am trying to accomplish:
I have a button that is in a relativeLayout tag and depending on this boolean i would like to reposition the button. So I have this:
<Button
android:id="#+id/startButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="64dip"
****************
android:layout_below="#{#bool/showAds ? #+id/adone : #+id/title_main}"
****************
android:layout_centerHorizontal="true"
android:textColor="#0080FF"
android:text="#string/start_btn_title" />
See what i want to to do? I want to layout the button below a layout called adone if the showAds boolean is true, otherwise place it below a layout called title_main. Whats the syntax for this as what I have here is not compiling. I get a compile error:
expression expected after the second # sign

The above is the same problem as in How to get dimensions from dimens.xml
None of the LayoutParams attributes have built-in support. As answered in the linked article, data binding of LayoutParams was thought to be too easy to abuse so it was left out of the built-in BindingAdapters. You are not abusing it, so you should add your own.
#BindingAdapter("android:layout_below")
public static void setLayoutBelow(View view, int oldTargetId, int newTargetId) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)
view.getLayoutParams();
if (oldTargetId != 0) {
// remove the previous rule
layoutParams.removeRule(RelativeLayout.BELOW);
}
if (newTargetId != 0) {
// add new rule
layoutParams.addRule(RelativeLayout.BELOW, newTargetId);
}
view.setLayoutParams(layoutParams);
}
As an aside, the #+id/adone in the binding syntax will not create the id. You should create the id in the View you're binding to.

Related

How do I layout my React Native Fire TV SubtitleView correctly?

I am working on a React Native implementation of the Bitmovin player using their Android SDK. At this stage, I'm not sure how specific this is to the Bitmovin player, but as they don't officially support React Native at this stage, I want to ask about this on SO first. This is a React Native UI Component with a custom view, using a layout file. I am trying to present a subtitle view on top of a player view, and I have based my layout on Bitmovin's simple examples. In fact I have simplified the layout even further:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.bitmovin.player.PlayerView
android:id="#+id/bitmovinPlayerView"
app:shutter_background_color="#android:color/transparent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/bootsplash_background">
<com.bitmovin.player.SubtitleView
android:id="#+id/bitmovinSubtitleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:foregroundGravity="center" />
</com.bitmovin.player.PlayerView>
</LinearLayout>
This presents the SubtitleView at the top of the screen. Nothing I have tried so far presents the SubtitleView at the bottom of the screen in the more common position. I have experimented with every single parameter on all of these elements, as far as I can tell. Here is the code that initialises the view:
public void init() {
inflate(context, R.layout.player_container, this);
StyleConfig styleConfig = new StyleConfig();
styleConfig.setUiEnabled(false);
PlayerConfig playerConfig = new PlayerConfig();
playerConfig.setStyleConfig(styleConfig);
playerView = findViewById(R.id.bitmovinPlayerView);
player = Player.create(context, playerConfig);
playerView.setPlayer(player);
player.on(SourceEvent.Loaded.class, this::onLoad);
player.on(PlayerEvent.Playing.class, this::onPlay);
player.on(PlayerEvent.Paused.class, this::onPause);
player.on(PlayerEvent.Seek.class, this::onSeek);
player.on(PlayerEvent.TimeChanged.class, this::onTimeChanged);
player.on(PlayerEvent.Destroy.class, this::onDestroy);
player.on(PlayerEvent.Seeked.class, this::onSeeked);
player.on(PlayerEvent.PlaybackFinished.class, this::onPlaybackFinished);
player.on(PlayerEvent.Ready.class, this::onReady);
player.on(SourceEvent.Error.class, this::onError);
player.on(SourceEvent.SubtitleChanged.class, this::onSubtitleChanged);
player.on(PlayerEvent.Error.class, this::onError);
subtitleView = findViewById(R.id.bitmovinSubtitleView);
subtitleView.setPlayer(player);
player.setVolume(100);
}
I have read that React Native styles the top-level view of a UI Component, so this is my only clue at this stage. I'm unsure how to respond to that info however...
EDIT: The problem is likely to be that dynamically updating view layouts in Android in React Native is not straightforward. This has been discussed at length here.
EDIT 2: I have tried to listen for global layout changes, which is one of the proposed workarounds for view layout issues:
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
requestLayout();
}
});
This is called as expected, but has no effect on the subtitleView, which still displays at the top of the player, which seems to be because it has a height of 0.
EDIT 3: Another suggested solution that didn't work for me:
private void setupLayoutHack() {
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
#Override
public void doFrame(long frameTimeNanos) {
manuallyLayoutChildren();
getViewTreeObserver().dispatchOnGlobalLayout();
Choreographer.getInstance().postFrameCallback(this);
}
});
}
private void manuallyLayoutChildren() {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
I called setupLayoutHack() in the constructor but saw no difference after applying those changes either :(
EDIT 4: My final attempt at fixing the SubtitleView layout was experimenting with measuring and laying out in various ways:
private void refreshViewChildrenLayout(View view){
view.measure(
View.MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(), View.MeasureSpec.EXACTLY));
view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
}
However, the height in all cases that I tried was 0, which meant nothing was altered. There is a solution mentioned in the above RN issue that suggests that the shadow node for the subtitle view should be overridden. So one way forward could be to build a new subtitle view that has that included.
However, at this stage it seems to me an easier approach to respond to subtitle cues in React Native and perform all display and styling there.
(There is also a lesser issue of how to make the background on either side of the text transparent, but the layout issue is far more important at this stage).
Disclaimer: I'm not very familiar with React Native and how it influences layout creation if at all.
However looking at your layout file, it indicates that the SubtitleView is the top child of the PlayerView, which is a FrameLayout, thus gets added at the top (left). By specifying android:layout_height="wrap_content" on the SubtitleView it will only take up space that is required by the view. In the Bitmovin sample, it is generated in code and therefore should inherit the attributes from the parent, which is a RelativeLayout with android:layout_weight="1" which results in stretching it's height to the space available.
Long story short, try setting the height of your SubtitleView to match_parent

TextInputLayout disable animation

I am using the TextInputLayout to implement the floating label pattern. However, when I set the text programmatically on the EditText, I still get the animation of the label moving from the control to the label - the same as if the user had clicked it.
I don't want this animation though if I set it programmatically, is this possible? Here is my code:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/root">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/editText1" />
</android.support.design.widget.TextInputLayout>
And in the onResume I do:
TextInputLayout root = (TextInputLayout)findViewById(R.id.root);
EditText et = (EditText)root.findViewById(R.id.editText1);
et.setText("Actual text");
root.setHint("Hint");
As of v23 of the support libs, a setHintAnimationEnabled method has been added. Here are the docs. So you can set this new attribute to false in the XML and then programmatically set it to true after you've finished populating the editText. OR simply handle it all programmatically, as needed.
So in your example, that would become something like:
TextInputLayout root = (TextInputLayout)findViewById(R.id.root);
root.setHintAnimationEnabled(false);
root.setHint("Hint");
EditText et = (EditText)root.findViewById(R.id.editText1);
et.setText("Actual text");
// later...
root.setHintAnimationEnabled(true);
Of course, be sure to open your Android SDK Manager and update your Android Support Library to rev. 23 and Android Support Repository to rev. 17 first and then add that to build.gradle via:
compile 'com.android.support:design:23.0.0'
Note that as the Design library depends on the Support v4 and AppCompat Support Libraries, those will be included automatically when you add the Design library dependency.
Put this attribute app:hintEnabled="false" here:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintEnabled="false">
It works for me
I have found an (unwieldy) way to do that. Looking into the source code of the TextInputLayout, I've discovered that the only occasion when the class doesn't update it's hint with animation is when the EditText is added to it. The only obstacle is that you can only add it once to the layout, once it is there, it will permanently be tied to it, and there's no way to remove it.
So here's the solution:
Create a TextInputLayout without the EditText. Programmatically or via XML inflation - doesn't matter, but it has to be empty.
Create the EditText and set its text to whatever you need.
Add the EditText to the TextInputLayout.
Here's an example:
TextInputLayout hintView = (TextInputLayout) findViewById(R.id.hint_view);
hintView.setHint(R.string.hint);
EditText fieldView = new EditText(hintView.getContext());
fieldView.setText(value);
hintView.addView(fieldView);
Unfortunately, if you want to set the text to something else without the animation, you will have to repeat all these steps, except for the creation of a new EditText (the last one can be reused). I hope Google can fix this, because it is really inconvenient, but for now that's what we have.
Update: this is, thankfully, fixed in the design library 23.0.0, so just update to that version, and you won't have to do all this crazy stuff.
You can disable it by using
app:hintAnimationEnabled="false"
I will give you simple code and you don't have to make all programmatically just add xlm attribute and that's all.
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintAnimationEnabled="true"
app:passwordToggleEnabled="true">
<EditText
android:id="#+id/password_field_activity_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:hint="#string/password"
android:inputType="textPassword"
android:padding="10dp"
android:textColor="#color/color_holo_light_gray"
android:textColorHint="#color/color_holo_light_gray"
android:textCursorDrawable="#drawable/cursor_drawable" />
</android.support.design.widget.TextInputLayout>
Hope it will help
I wrote a small method to run after loading the view hierarchy that disables animation on initial display but enables it after wards. Add this to your Base Activity/Fragment/View and it will solve it issue.
private void setTextInputLayoutAnimation(View view) {
if (view instanceof TextInputLayout) {
TextInputLayout til = (TextInputLayout) view;
til.setHintAnimationEnabled(false);
til.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override public boolean onPreDraw() {
til.getViewTreeObserver().removeOnPreDrawListener(this);
til.setHintAnimationEnabled(true);
return false;
}
});
return;
}
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
setTextInputLayoutAnimation(child);
}
}
}
I check out ideas about support library v23 - it's does not work yet :( Programmatically inserting of EditText is unusable if you wish to use databinding labrary
In result of research I found solution without full disabling of animation:
layout.xml
<android.support.design.widget.TextInputLayout
android:id="#+id/text_til"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/product_title_placeholder">
<android.support.design.widget.TextInputEditText
android:id="#+id/text_tiet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={bindingText}"/>
</android.support.design.widget.TextInputLayout>
Activity.java
final TextInputLayout textLayout = (TextInputLayout) findViewById(R.id.text_til);
if(binding.getText() != null) {
// disable animation
textLayout.setHintAnimationEnabled(false);
final TextInputEditText editText = (TextInputEditText) titleLayout.findViewById(R.id.text_tiet);
editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View view, boolean b) {
// enable animation after layout inflated
textLayout.setHintAnimationEnabled(true);
editText.setOnFocusChangeListener(null);
}
});
}
// manage focus for new binding object
((View)textLayout .getParent()).setFocusableInTouchMode(binding.getText() == null);
It's looks not prettily, but it works :)

Change value in layout.xml conditionally in Android

I'm trying to get android:layout_marginEnd="-6dp" to change to 2dp conditionally in:
<FrameLayout
android:id="#+id/wifi_combo"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginEnd="-6dp"
>
My condition is:
if (mSignalClusterStyle == STYLE_ALWAYS) {
mMobileType.setVisibility(View.VISIBLE);
} else if (mWifiVisible) {
mMobileType.setVisibility(View.GONE);
}
I would like to add a line to the if statement that overrides the -6dp with 2dp in the xml file.
I have explored setMarginEnd(), but there are very few resources on this, seeing as it is only a year old, and I keep getting a compile error with it.
What is the best way to change android:layout_marginEnd on a condition programmatically?
FrameLayout layout = (FrameLayout) findViewById(R.id.wifi_combo);
FrameLayout.LayoutParams params = (LayoutParams) layout.getLayoutParams();
params.setMarginEnd(2);
The correct way to set the endMargin is using setMarginEnd() only, added in API 17.
http://developer.android.com/reference/android/view/ViewGroup.MarginLayoutParams.html#setMarginEnd(int)
BTW, what compile errors you are getting?

MvxSpinner/MvxListView doesn't work with MvvmCross Light (Chimp) from axml

In Android project I'm trying to add databindings using CrossLight part of MvvmCross.
Bindings to standard TextView/Buttons work great. But simplest markup with Mvx.Control:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<Mvx.MvxListView />
</LinearLayout>
Gives an error
"Binary XML file line #1: Error inflating class Mvx.MvxListView"
The same thing is with Mvx.Spinner.
However, when instantiating it from code in Activity.OnCreate:
_bindingContext = new MvxAndroidBindingContext(this, new LayoutInflaterProvider(LayoutInflater), _viewModel);
var view = (LinearLayout)_bindingContext.BindingInflate(Resource.Layout.Main, null);
SetContentView(view);
var spinner = new MvxSpinner(this, null, new MvxAdapter(this, _bindingContext));
view.AddView(spinner);
Everything works great (including bindings). What am I doing wrong? Is this scenario supported in general?
Or maybe I should reference anything else except nuget MvvmCross.HotTuna.CrossCore?
P.S. Haven't found any samples with custom controls and CrossLight neither on github, nor on N+1 videos
If you want to use namespace abbreviations within your non-MvvmCross application, then you'll need to add those abbreviations. This can be done using a custom binding builder or using a 'light' setup step like:
var viewResolver = Mvx.Resolve<IMvxAxmlNameViewTypeResolver>();
viewResolver.ViewNamespaceAbbreviations["Mvx"] = "Cirrious.MvvmCross.Binding.Droid.Views";
viewResolver.ViewNamespaceAbbreviations["MyApp"] = "MyApp.Controls";
When doing this within a full MvvmCross application, then you can override the Setup property ViewNamespaceAbbreviations
protected override IDictionary<string, string> ViewNamespaceAbbreviations
{
get
{
var toReturn = base.ViewNamespaceAbbreviations;
toReturn["MyApp"] = "MyApp.UI.Droid.Controls";
return toReturn;
}
}
When markup was changed to using the full namespace and layout_width and layout_height attribute was added it started to work!
<Cirrious.MvvmCross.Binding.Droid.Views.MvxSpinner
android:id="#+id/MySpinner"
android:layout_width="fill_parent"
android:layout_height="20dp"
/>
It was found when I switched to default Android inflater and it was complaining about missing layout_width in Exceptions.

How to overlay a button programmatically?

What I would like to accomplish is to, at runtime, place a button in the middle of the screen, as the very top layer, overlaying anything below it. (It's not big, so it will not completely cover the screen, just whatever happens to be below it.)
I looked at creating a custom dialog, however that blocks all other user input. I want all of the views below this new button to act normally and respond to the user, but I just want to add (and later remove) the button above everything.
Hopefully that makes sense. I'm just wondering what might be the best approach to look into?
Use a FrameLayout, with the button as it's 2nd child. Set it to GONE when you don't want it visible.
I had to overlay a simple layout programmatically on top of any visible activity. Normal activity layout xmls don't know anything about the overlay. Layout had one textview component but could have any structure you see fit. This is my overlay layout.
res/layout/identity.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/identitylayout"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" >
<TextView
android:id="#+id/identityview"
android:padding="5dp"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textColor="#FFFFFF" android:background="#FF6600"
android:textSize="30dp"
/>
</RelativeLayout>
Overlay is shown on top of the existing content, after timeout is deleted from the screen. Application calls this function to display overlay.
private void showIdentity(String tag, long duration) {
// default text with ${xx} placeholder variables
String desc = getString(R.string.identity);
desc = desc.replace("${id}", reqId!=null ? reqId : "RequestId not found" );
desc = desc.replace("${tag}", tag!=null ? tag : "" );
desc = desc.trim();
// get parent and overlay layouts, use inflator to parse
// layout.xml to view component. Reuse existing instance if one is found.
ViewGroup parent = (ViewGroup)findViewById(R.id.mainlayout);
View identity = findViewById(R.id.identitylayout);
if (identity==null) {
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
identity = inflater.inflate(R.layout.identity, parent, false);
parent.addView(identity);
}
TextView text = (TextView)identity.findViewById(R.id.identityview);
text.setText(desc);
identity.bringToFront();
// use timer to hide after timeout, make sure there's only
// one instance in a message queue.
Runnable identityTask = new Runnable(){
#Override public void run() {
View identity = findViewById(R.id.identitylayout);
if (identity!=null)
((ViewGroup)identity.getParent()).removeView(identity);
}
};
messageHandler.removeCallbacksAndMessages("identitytask");
messageHandler.postAtTime(identityTask, "identitytask", SystemClock.uptimeMillis()+duration);
}
Timer messageHandler is member of main Activity instance (private Handler messageHandler) where I put all scheduled tasks. I am using Android 4.1 device lower than that I don't know what happens.

Categories

Resources