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
Related
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.
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:
I've created my own set of alphabetical sounds (replacing the regular a,b,c,..z). I'd like to customize the talkback functionality so it uses my own sounds instead of the built-in ones when using my own app. This means that when a visually impaired user would try to read the content of a label in my app he'd hear my own sounds. Is this possible? If so, what would be the right way to achieve this?
Assuming your own sounds have a "phonetic spelling" this would be pretty easy. Let's say you had the letter A and you wanted it to pronounce as "A" and not the sound "uh". You could simply replace "A" with "ay" and TalkBack would pronounce it correctly. Assuming this is the case, what you want to do is very easy. If what you have created is actual sounds, and cannot simply use phonetic spellings as I am assuming, as alanv has said, this is impossible. Or at least, involves more than just changes to your app!!!
What you want to do is intercept all accessibility events coming from your application, and then when you intercept the events, replace the content description with your phonetically spelled content description. The tricky part is emulating TalkBack logic for grabbing text from Accessibility events, so that you grab the correct text! Otherwise you end up modifying the wrong string, or just nothing.
If you attach this accessibility delegate to the views within your view heirarchy, you can override the content description of the accessibility node infos, and replace it with your phonetic pronunciations. I attached all relevant portions of my solution. There may be a way to get this working by only mucking with the root view's accessibility delegate, and not the entire view hierarchy. I may investigate more later, but this works just dandy, and is a linear operation on load (and view addition for dynamic content) which isn't bad at all.
Add this code to your onCreate method, and modify the "convertText" function to suit your needs, and you should be all set!
final View.AccessibilityDelegate accessiblityDelegate = new View.AccessibilityDelegate() {
String convertText(String argString) {
//Do your phonetic conversion in here!
//A little Regex. A little String replacement and you're golden!
return argString;
}
#Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo nodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, nodeInfo);
String text = null;
if (nodeInfo.getContentDescription() != null) {
text = convertText(nodeInfo.getContentDescription().toString());
} else if (nodeInfo.getText() != null) {
text = convertText(nodeInfo.getText().toString());
} else if (host instanceof TextView) {
TextView textView = (TextView)host;
text = convertText(textView.getText().toString());
}
if (text != null) nodeInfo.setContentDescription(text);
}
};
rootView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
addAccessibilityDelegateToViews(v);
}
private void addAccessibilityDelegateToViews(View v) {
v.setAccessibilityDelegate(accessiblityDelegate);
if (v instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup)v;
for (int i = 0; i < viewGroup.getChildCount(); ++i) {
View view = viewGroup.getChildAt(i);
addAccessibilityDelegateToViews(view);
}
}
}
});
Xamarin.Forms is very new and very exciting, but for now I see that it has limited documentation and a few samples. I'm trying to make an app with an interface similar to the "MasterDetailPage" one, but also having a right Flyout view, not only the left one.
I've seen that this is not possible using the current API, and so my approach was this:
Create a shared GestureRecognizer interface.
In Android app and iOS in bind this interface to the UIGestureRecognizer on iOS or the OnTouch method on the android.
For iOS this is working but for Android the touch listener over the activity doesn't seem to work.
Is my approach good? Maybe there is another good method to capture touch events directly from the shared code? Or do you have any ideas why the public override bool OnTouchEvent doesn't work in an AndroidActivity?
For Xamarin.Forms swipe gesture recognizer add SwipeGestureRecognizer
<BoxView Color="Teal" ...>
<BoxView.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Swiped="OnSwiped"/>
</BoxView.GestureRecognizers>
</BoxView>
Here is the equivalent C# code:
var boxView = new BoxView { Color = Color.Teal, ... };
var leftSwipeGesture = new SwipeGestureRecognizer { Direction = SwipeDirection.Left };
leftSwipeGesture.Swiped += OnSwiped;
boxView.GestureRecognizers.Add(leftSwipeGesture);
For more check here : https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/gestures/swipe
The MasterDetailPage, and other shared elements, are just containers for view renderers to pick up. You best option would be to create a custom LRMasterDetailPage (left-right..) and give it properties for both the DetailLeft and DetailRight. Then you implement a custom ViewRenderer per platform for this custom element.
The element:
public class LRMasterDetailPage {
public View LeftDetail;
public View RightDetail;
public View Master;
}
The renderer:
[assembly:ExportRenderer (typeof(LRMasterDetailPage), typeof(LRMDPRenderer))]
namespace App.iOS.Renderers
{
public class LRMDPRenderer : ViewRenderer<LRMasterDetailPage,UIView>
{
LRMasterDetailPage element;
protected override void OnElementChanged (ElementChangedEventArgs<LRMasterDetailPage> e)
{
base.OnElementChanged (e);
element = e.NewElement;
// Do someting else, init for example
}
protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Renderer")
return;
base.OnElementPropertyChanged (sender, e);
if (e.PropertyName == "LeftDetail")
updateLeft();
if (e.PropertyName == "RightDetail")
updateRight();
}
private void updateLeft(){
// Insert view of DetailLeft element into subview
// Add button to open Detail to parent navbar, if not yet there
// Add gesture recognizer for left swipe
}
private void updateRight(){
// same as for left, but flipped
}
}
}
I advise you to use the CarouselView approach, f.e. you can use already existing solutions with carousel view which are supports swipe gestures. So in result your view will be wrapped into this carousel view control
I am interested in populating a screen/activity with a user defined number of same views. Each view will have the exact same layout: couple TextViews and few Buttons. The thing is that each button will control what each TextView will display.
The way I was thinking to implement it was to have one XML and one Java class. Then dependimg on the number the user inputs, populate the screen with that many same views (using a for loop). The question is, can it be done? how? am I thinking about it in the right way?
Please help with any input or thoughts, code examples will be great too.
of course it can be done.
I think the easiest for your situation, plus you can then easily extend, is to create some helper functions that take care of:
1) creating a empty screen
2) create a button for a screen
3) create a textview for a screen
and finally
4) create a screen and populate it
You have to decide the proper Root element for your Views, depending on the child arragement you need. For simplicity let's choose a LinearLayout, but for a RelativeLayout or TableLayout the example is the same, it only changes that when you add the elements, you have to use additional parameters to properly place them.
Note that the function to create an empty custom view returns a ViewGroup ("where all layouts derive from"). This way, you always work with ViewGroups and just define the screen layout type once, inside createCustomView. So you can change the type of screens just there, and the rest of code will work ...
Here is some code for your inspiration:
private ViewGroup createCustomView(Context context) {
LinearLayout myCoolNewView=new LinearLayout(context); // or RelativeLayout, etc..
return myCoolNewView;
}
private Button createButton(Context context, String buttonText) {
Button newButton=new Button(context);
newButton.setText(buttonText);
return newButton;
}
private TextView createText(Context context, String initialText) {
TextView newText=new TextView(context);
newText.setText(buttonText);
return newText;
}
private ViewGroup createScreen(Context context, int numberOfButtons, int numberOfTextfields) {
ViewGroup newScreen=createCustomView(context);
TextView[] textViews=new TextView[numberOfTextFields];
for (int i=0; i<numberOfTextfields; i++) {
textViews[i]=createText(context, "hi i am text "+i);
newScreen.addView(textViews[i]); // you ideally provide here layoutparams to properly place your buttons
}
for (int j=0; i<numberOfButtons; j++) {
Button button=createButton(context, "hi i am button "+j);
button.setOnClickListener(new OnClickListener() {
public void onClick (View clickedView) {
// here you have a button keypress and you know all the textviews
textView[i%j].setText("hey you pressed me");
}
});
newScreen.addView(button);
}
return newScreen;
}
So now you can:
ViewGroup screen1=createScreen(context, 10, 10);
ViewGroup screen2=createScreen(context, 5, 3);
ViewGroup screen3=createScreen(context, 2, 5);
and add the screens to a parent layout, to a ViewFlipper, to a ViewSwitcher, etc... like this:
ViewGroup parentLayoutOfAllScreens=findViewById(R.id.root_of_screens);
parentLayoutOfAllScreens.addView(screen1);
parentLayoutOfAllScreens.addView(screen2);
parentLayoutOfAllScreens.addView(screen3);
In the XML you just have to create the root layout, and name it root_of_screens...
good coding !!! I suppose there'll be some errors in the code above, just typed it here, but I hope you get the idea and tweak it to suit your needs!
EDIT : v2.0 : Extending a View
Create a new .java named "MyCoolScreen.java" or whatever name, in the same folder where your activity is (for simplicity):
package ........
public class MyCoolScreen extends LinearLayout {
/** Now every view holds its own buttons, and they are private, it's good for encapsulating */
private TextView[] mTextViews; // <-- as a convention, members should start with "m"
private Button[] mButtons;
private UserPressedButtons mUserPressedButtonsListener; // See below
/** The following constructors must always be present for a custom view, and must always call super */
public MyCoolScreen(Context context) {
// This is the constructor you will use when creating your view programmatically
super(context);
}
public MyCoolScreen(Context context, AttributeSet attrs) {
// This is the constructor Android calls when you include your custom view in an XML
// You can do this too!!
// The ATTRS will then include your numberofbuttons and numberoftextfields from the XML
// this is beyond the example, but read about it, it's interesting
super(context, attrs); // this MUST ALWAYS be here for custom views, or they will not work.
// it tells the parent view to continue the construction.
}
public MyCoolScreen(Context context, AttributeSet attrs, int defStyle) {
// Another constructor Android calls from the XML
super(context, attrs, defStyle);
}
/** We create an "init" method to initialize this view from outside */
public void init(int numberOfTextViews, int numberOfButtons) {
createScreen(numberOfTextViews, numberOfButtons);
}
/** This is the same */
private Button createButton(Context context, String buttonText) {
Button newButton=new Button(context);
newButton.setText(buttonText);
return newButton;
}
/** This is the same */
private TextView createText(Context context, String initialText) {
TextView newText=new TextView(context);
newText.setText(buttonText);
return newText;
}
/** We tweak this function so it doesnt return a view, but rather fills up this one :) */
private void createScreen(int numberOfButtons, int numberOfTextfields) {
ViewGroup newScreen=this; // It's this view the one we gonna fill up!
mTextViews=new TextView[numberOfTextfields];
mButtons=new Button[numberOfButtons];
Context context=getContext(); // Views always know their context after constructed
for (int i=0; i<numberOfTextfields; i++) {
mTextViews[i]=createText(context, "hi i am text "+i);
newScreen.addView(textViews[i]); // you ideally provide here layoutparams to properly place your buttons
}
for (int j=0; i<numberOfButtons; j++) {
Button button=createButton(context, "hi i am button "+j);
button.setId(j);
button.setOnClickListener(new OnClickListener() {
public void onClick (View clickedView) {
// here you have a button keypress and you know all the textviews
if (mUserPressedButtonsListener!=null) mUserPressedButtonsListener.OnButtonPressed(j);
textView[i%j].setText("hey you pressed me");
}
});
mButtons[j]=button;
newScreen.addView(button);
}
}
public interface UserPressedButtons {
public void OnButtonPressed(int buttonNumber);
}
public void setUserPressedButtonsListener (UserPressedButtons listener) {
mUserPressedButtonsListener=listener;
}
}
Ok, so now to use this, in your Activity you can do:
import ....... .MyCoolScreen;
import ....... .MyCoolScreen.UserPressedButtons;
.
.
.
MyCoolScreen screen1=new MyCoolScreen(context);
screen1.init(5,5); // initializes the screen.
myRootLayout.addView(screen1);
What's cool about this, is now functionality is totally encapsulated in your custom view. And it resides in another .java, so your activity code is very clean, and you can even expand the View functionality without making it ugly.
It's also a common practice to create interfaces and listeners for your views to communicate with the outside world, so for example, we can do:
screen1.setUserPressedButtonsListener(new MyCoolScreen.UserPressedButtons() {
#Override
public void OnButtonPressed (int number) {
// you know the user pressed button "number", and you can do stuff about it without
// having to include it inside the MyCoolScreen class. Of course in your example you
// don't need this at the moment, because the View will modify its textfield, but suppose
// one of the buttons is "rocket launch" , that is something you will handle at the activity level, ie.
if (number==ROCKET_LAUNCH) RocketLauncher.setTarget(10,10).launch(); // Your MyCoolScreen doesnt know how to launch rockets, but your activity maybe yes...
}
});
You can do all kinds of cool things with your new custom view. For example, you could define:
#Override
public void OnDraw(Canvas c) {
c.drawEllipse ...
c.drawRectangle ....
}
And you can paint circles, lines, etc... over your textfields & buttons :) For this to work, you have to put
setWillNotDraw(false) on the constructor.
There might be errors, just typed the code here, but I hope it helps you!
Add and Remove Views in Android Dynamically?
this will helps to you most...