Android View.post(Runnable) - Running but not affecting UI - android

I'm trying to add a view to the toolbar in my Android Activity. The below "decorView" is a RelativeLayout child view of the actionbar/toolbar.
Defining the Container:
ActionBar actionBar = getSupportActionBar();
if (actionBar == null) {
Toolbar toolBar = (Toolbar) findViewById(R.id.action_bar);
setSupportActionBar(toolBar);
}
actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setCustomView(getActionBarLayoutResourceId());
this.mActionBarView = (RelativeLayout) actionBar.getCustomView()
.findViewById(R.id.relative_layout_action_bar);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setHomeButtonEnabled(false);
actionBar.setDisplayUseLogoEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayHomeAsUpEnabled(false);
} else {
log("ActionBar is null");
}
Adding the view:
final ViewGroup decorView = this.mActionBarView;
Runnable postDecorHeaderView = new Runnable() {
#Override
public void run() {
if (decorView.getWindowToken() != null) {
// The Decor dView has a Window Token, so we can add the
// HeaderView!
decorView.addView(mHeaderView);
} else {
try {
Thread.sleep(1000); // 1 second
} catch (InterruptedException e) {
log(e.getClass() + ": " + e.getMessage());
}
// The Decor View doesn't have a Window Token yet, post
// ourselves again...
decorView.post(this);
}
}
};
decorView.post(postDecorHeaderView);
I have verified that decorView's child count increases after this method completes. Unfortunately, my "mHeaderView" does not show up, nor does it appear in view hierarchy analysis. I have tried other UI updates like changing a TextView's text to no avail.
I have verified the identity of the decorView from within the runnable. If I log the runnable, I see it running, I verify that it's running on the main UI thread... but no visual updates occur.

By grabbing the toolbar from the existing view, the custom layout was already present in the view. So I did not need to additionally "setCustomView". I'm not sure where this second custom view layout ended up on the screen (even using debug tools, I couldn't find the layout), but trying to post to and add a child view were not working.
Ultimately, all I did was change the mActionBarView setter to:
this.mActionBarView = (RelativeLayout) findViewById(R.id.relative_layout_action_bar);

Related

How to clear animation of adding/removing actionbar?

At my application I am using container where I change several fragments. At some fragments I have to hide actionbar at parent activity. I managed to do it, but the process of hiding and viewing toolbar is supported with weird and uncomfortable animation. I hide and show toolbar via:
Objects.requireNonNull(getSupportActionBar()).hide();
Maybe I can clear animation?
I managed to find a solution for my problem with this method:
public static void disableShowHideAnimation(ActionBar actionBar) {
try
{
actionBar.getClass().getDeclaredMethod("setShowHideAnimationEnabled", boolean.class).invoke(actionBar, false);
}
catch (Exception exception)
{
try {
Field mActionBarField = actionBar.getClass().getSuperclass().getDeclaredField("mActionBar");
mActionBarField.setAccessible(true);
Object icsActionBar = mActionBarField.get(actionBar);
Field mShowHideAnimationEnabledField = icsActionBar.getClass().getDeclaredField("mShowHideAnimationEnabled");
mShowHideAnimationEnabledField.setAccessible(true);
mShowHideAnimationEnabledField.set(icsActionBar,false);
Field mCurrentShowAnimField = icsActionBar.getClass().getDeclaredField("mCurrentShowAnim");
mCurrentShowAnimField.setAccessible(true);
mCurrentShowAnimField.set(icsActionBar,null);
}catch (Exception e){
//....
}
}
}
link.

Android smoothScrollTo isn't smooth

I'm trying to scroll to a given layout in a ScrollView. My XML is basically composed of a ScrollView implementing various RelativeLayout and I want to programatically scroll to a given one.
I have the following code in my Activity's onCreate method :
// Defining my view
sv = (ScrollView)findViewById(R.id.reward_scroll_view);
// Get Layout's id from intent
Bundle extras = getIntent().getExtras();
if (extras != null) {
idToScroll = extras.getInt("uiToScroll");
sv.post(new Runnable() {
public void run() {
// Scroll to the passed element
RelativeLayout layout = (RelativeLayout) findViewById(idToScroll);
sv.smoothScrollTo(0, layout.getTop());
}
});
}
The "auto-scroll" to a given anchor is working but there is no "smooth" effect, just a raw scroll to the layout. What am I missing ?
You are overriding post method of view.
Scroll view not working smoothly with in UI Thread or Post
Look at this question
I resolved my problem thanks to Aamir's comment, here is how my final code looks like :
Bundle extras = getIntent().getExtras();
if (extras != null) {
// Get Layout's id from intent
idToScroll = extras.getInt("uiToScroll");
layout = (RelativeLayout) findViewById(idToScroll);
// Start 1 second timer
new CountDownTimer(1000, 20) {
public void onTick(long millisUntilFinished) {
// Nothing...
}
// When over, start smoothScroll
public void onFinish() {
sv.smoothScrollTo( 0, layout.getTop() );
}
}.start();
}
My view may have just need some delay to display the scroll effect properly.

Set OnClick Listener on Action Bar Title in Android

I am working on android application where I am using ActionBar so there one is navigation drawer icon to open it and title of ActionBar in ActionBar. I want to set a click listener on title of ActionBar such that it start a new Activity and set click listener different on navigation drawer icon to open navigation drawer menu.
I achieved a click on navigation drawer icon but when I click on title of ActionBar title also then it open the navigation drawer menu. Is there any way to set different click listener on title of ActionBar.
Thanks in advance.
Try adding this code under the onCreate() function. This will grab the resource the action bar title is under, and assign it an id you can use to add an OnClickListener to. Let me know how it goes!
final int abTitleId = getResources().getIdentifier("action_bar_title", "id", "android");
findViewById(abTitleId).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Do something
}
});
You could use a custom layout for the title and assign a listener to it:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getActionBar();
if (actionBar != null) {
// Disable the default and enable the custom
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
View customView = getLayoutInflater().inflate(R.layout.actionbar_title, null);
// Get the textview of the title
TextView customTitle = (TextView) customView.findViewById(R.id.actionbarTitle);
// Change the font family (optional)
customTitle.setTypeface(Typeface.MONOSPACE);
// Set the on click listener for the title
customTitle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.w("MainActivity", "ActionBar's title clicked.");
}
});
// Apply the custom view
actionBar.setCustomView(customView);
}
}
actionbar_title.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:id="#+id/actionbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:text="#string/app_name"/>
</LinearLayout>
I think Simas's answer is the best one, but here's a hacky version in case you prefer that.
ViewTools.findActionBarTitle(getWindow().getDecorView()).setOnClickListener(...);
This one should be universal in that it works with:
stock Android ActionBar
Theme.AppCompat support ActionBar
v21-style setActionBar
use <Toolbar android:id="#+id/action_bar"
or pass in the inflated Toolbar as root
v21-style setSupportActionBar
use <android.support.v7.widget.Toolbar android:id="#id/action_bar"
or pass in the inflated Toolbar as root
custom Toolbar implementations may need a little adjustment,
but then you could encapsulate this in that custom class.
Though I only tested with support:v22.
/** #param root usually Activity.getWindow().getDecorView() or your custom Toolbar */
public static #Nullable View findActionBarTitle(#NonNull View root) {
return findActionBarItem(root, "action_bar_title", "mTitleTextView");
}
/** #param root usually Activity.getWindow().getDecorView() or your custom Toolbar */
public static #Nullable View findActionBarSubTitle(#NonNull View root) {
return findActionBarItem(root, "action_bar_subtitle", "mSubtitleTextView");
}
private static #Nullable View findActionBarItem(#NonNull View root,
#NonNull String resourceName, #NonNull String toolbarFieldName) {
View result = findViewSupportOrAndroid(root, resourceName);
if (result == null) {
View actionBar = findViewSupportOrAndroid(root, "action_bar");
if (actionBar != null) {
result = reflectiveRead(actionBar, toolbarFieldName);
}
}
if (result == null && root.getClass().getName().endsWith("widget.Toolbar")) {
result = reflectiveRead(root, toolbarFieldName);
}
return result;
}
#SuppressWarnings("ConstantConditions")
private static #Nullable View findViewSupportOrAndroid(#NonNull View root, #NonNull String resourceName) {
Context context = root.getContext();
View result = null;
if (result == null) {
int supportID = context.getResources().getIdentifier(resourceName, "id", context.getPackageName());
result = root.findViewById(supportID);
}
if (result == null) {
int androidID = context.getResources().getIdentifier(resourceName, "id", "android");
result = root.findViewById(androidID);
}
return result;
}
#SuppressWarnings("unchecked")
public static <T> #Nullable T reflectiveRead(#NonNull Object object, #NonNull String fieldName) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T)field.get(object);
} catch (Exception ex) {
Log.w("HACK", "Cannot read " + fieldName + " in " + object, ex);
}
return null;
}
If you are using Toolbar with support v7:21.
Check out the following code:
Field titleField = Toolbar.class.getDeclaredField("mTitleTextView");
titleField.setAccessible(true);
TextView barTitleView = (TextView) titleField.get(mToolbar);
barTitleView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
You can do this easily using Toolbar. Define toolbar in layout xml file as given below:
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="?colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<TextView
android:id="#+id/toolbarTitle"
style="#style/TextAppearance.Widget.AppCompat.Toolbar.Title"
android:background="?attr/selectableItemBackground"
android:layout_width="wrap_content"
android:gravity="center_vertical"
android:layout_height="match_parent" />
</android.support.v7.widget.Toolbar>
Then you can set the listener in Activity using this code:
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
TextView toolbarTitle= (TextView) findViewById(R.id.toolbarTitle);
toolbarTitle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// DO SOMETHING HERE
}
});
If you want to use the currently existing ActionBar and not the Toolbar, use the following:
ActionBar actBar = getSupportActionBar();
if(actBar != null) {
actBar.setTitle(R.string.your_ab_title);
}
//Set actions to take when the AB is clicked
Toolbar ab = findViewById(R.id.action_bar);
if(ab != null){
for (int i= 0; i < ab.getChildCount(); i++){
View child = ab.getChildAt(i);
if(child instanceof TextView || child instanceof ImageView) {
child.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String url = "http://www.HoverDroids.com";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
}
});
}
}
}
If you know the actual text that is in your Title, and you are reasonably sure that no other TextView on the screen shares that title, you can use a recursive View tree search to find it.
This is a great solution because it doesn't require reflection of internal knowledge of how to Toolbar is constructed, and gives you direct access to the TextView.
#Nullable
public static TextView findTextViewWithText(#Nullable View toCheck, String toFind) {
if (toCheck instanceof TextView) {
String foundText = ((TextView) toCheck).getText().toString();
if (foundText.equals(toFind)) {
return (TextView) toCheck;
}
} else if (toCheck instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) toCheck).getChildCount(); i++) {
TextView found = findTextViewWithText(((ViewGroup) toCheck).getChildAt(i), toFind);
if (found != null) {
return found;
}
}
}
return null;
}
The most reliable view to call this on is the decor view but feel free to experiment what works best for your purposes, your mileage may vary.
View found = findTextViewWithText(
getActivity().getWindow().getDecorView(), "My Title");
if (found != null) {
// Do something, like set a click listener
}
I know its too late, but for or those who use SupportActionBar like this and still have not found a clean solution:
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
For the default configuration without logo and custom views, 1st item (index 0) will be the Home/Back ImageView, 2nd item will be our Title TextView and 3rd item will be the OptionMenu Imageview.
Getting child at index 1 would return title. Adding an OnClickListener to the child will make it work like a chram:
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.getChildAt(1).setOnClickListener(v -> {
// title is clicked, call ur function here
// can also verify that the view is title itself by converting it to textview
try {
String title = ((TextView)v).getText().toString();
// title will be your activity title
} catch (Exception e) {
e.printStackTrace();
// if you got an exception, the view is not title.
// Check changing the index, in case you have custom views in the toolbar.
}
});
You can do this easily using Toolbar. Define toolbar in layout xml file as given below:
<android.support.v7.widget.Toolbar
android:id="#+id/MainActivityToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#string/app_name"
android:textSize="30sp"
tools:ignore="RelativeOverlap"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
/>
<Button
android:id="#+id/LogOutButton"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
android:text="#string/logout" />
</RelativeLayout>
</android.support.v7.widget.Toolbar>
Then you can set the listener in Activity using this code:
setSupportActionBar((Toolbar) findViewById(R.id.MainActivityToolbar));
logOutButton = findViewById(R.id.LogOutButton);
logOutButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//define your function for logout or something else
LogOut();
}
});
I know it's very late to comment here but I came across this question when I searched for how to add OnClick for Action bar title.
Below is what I found and worked for me, hope it will help someone like me.
I wrote it for a fragment in my app.
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
actionBar.setTitle("");
((AppCompatActivity) getActivity()).setSupportActionBar((Toolbar) getActivity().findViewById(R.id.toolbar));
TextView toolbarTitle = (TextView) getActivity().findViewById(R.id.toolbarTitle);
toolbarTitle.setText("New title");
toolbarTitle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Action bar title clicked
}
});
actionBar.show();

Android: safe way to hide navigation spinner in ActionBar?

I would like to show a spinner in my ActionBar, using ActionBar.NAVIGATION_MODE_LIST, but I would like it to hide/show based on some application context. I have found that I can remove it from the ActionBar with getActionBar().setNavigationMode(-1), however I don't know if this is a good idea.
Any feedback on if this is safe or if there is a safer alternative?
Maybe this is more accepted:
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setDisplayShowTitleEnabled(false);
If you stick to ActionBar.NAVIGATION_MODE_LIST, you will have to set navigation listener every time you want to show your spinner back. That is obviously not the best soluton.
Instead, you may want to use ActionBar.setCustomView() to set spinner navigation (reference).
Here is some sample code where you set the spinner:
Spinner navigationSpinner = new Spinner(this);
navigationSpinner.setAdapter(yourSpinnerAdapter);
// Here you set navigation listener
navigationSpinner.setOnItemSelectedListener(yourSpinnerNavigationListener);
getActionBar().setCustomView(navigationSpinner);
getActionBar().setDisplayShowCustomEnabled(true);
Then, when you want to show/hide it you simply change it's visibility:
getActionBar().getCustomView().setVisibility(View.INVISIBLE);
Just modify your implementation of ActionBarDrawerToggle like this:
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, slideOffset);
if (slideOffset == 0) { // 0 = drawer is closed
setActionBarNavigationVisibility(activity, true); //show Tabs when Drawer is closed
}
}
public void onDrawerStateChanged(int newState) {
super.onDrawerStateChanged(newState);
//hides Tabs right after Drawer starts opening
if (DrawerLayout.STATE_DRAGGING == newState || DrawerLayout.STATE_SETTLING == newState) {
setActionBarNavigationVisibility(activity, false);
}
}
Where method setActionBarNavigationVisibility is considering all navigation modes (you can delete code for unnecesarry navigation modes):
public static void setActionBarNavigationVisibility(Activity activity, boolean visible) {
try {
/* 1. --- If the navigation items are showing in ActionBar directly. We have 3 options Spinner, Tabs, and CustomNav ---
(When Tabs are showing BELOW ActionBar, is handled at the end) */
int actionViewResId = Resources.getSystem().getIdentifier("action_bar", "id", "android"); // #see http://stackoverflow.com/questions/20023483/how-to-get-actionbar-view
View actionBarView = activity.findViewById(actionViewResId); // returns instance of com.android.internal.widget.ActionBarView (inaccessible)
if (actionBarView != null) {
int visibility = visible ? View.VISIBLE : View.INVISIBLE; // not GONE, so it still takes space in ActionBar layout
// handle tabs navigation
Field mTabScrollViewField = actionBarView.getClass().getDeclaredField("mTabScrollView");
if (mTabScrollViewField != null) {
mTabScrollViewField.setAccessible(true);
View mTabScrollView = (View) mTabScrollViewField.get(actionBarView); // instance of com.android.internal.widget.ScrollingTabContainerView (inaccessible)
if (mTabScrollView != null)
mTabScrollView.setVisibility(visibility);
}
// handle Spinner navigation
Field mSpinnerField = actionBarView.getClass().getDeclaredField("mSpinner"); // resp. mListNavLayout
if (mSpinnerField != null) {
mSpinnerField.setAccessible(true);
View mSpinner = (View) mSpinnerField.get(actionBarView); // instance of android.widget.Spinner
if (mSpinner != null)
mSpinner.setVisibility(visibility);
}
// handle Custom navigation
Field mCustomNavViewField = actionBarView.getClass().getDeclaredField("mCustomNavView"); // resp. mListNavLayout
if (mCustomNavViewField != null) {
mCustomNavViewField.setAccessible(true);
View mCustomNavView = (View) mCustomNavViewField.get(actionBarView);
if (mCustomNavView != null)
mCustomNavView.setVisibility(visibility);
}
}
// 2. --- If the Tabs are BELOW ActionBar (narrow screens) ---
ViewParent actionBarContainer = actionBarView.getParent(); // parent of ActionBarView is com.android.internal.widget.ActionBarContainer (inaccessible)
Field mTabContainerField = actionBarContainer.getClass().getDeclaredField("mTabContainer");
if (mTabContainerField != null) {
mTabContainerField.setAccessible(true);
View mmTabContainer = (View) mTabContainerField.get(actionBarContainer);
if (mmTabContainer != null)
mmTabContainer.setVisibility(visible ? View.VISIBLE : View.GONE); // now use GONE, so the mTabContainer below Actionbar does not take space in layout
}
} catch (Exception ex) {
// TODO Handle exception...
}
}

How to change title bar color without theme

How I can change background color of titlebar without using theme. tnks in Android.
Try with the following code
View titleView = getWindow().findViewById(android.R.id.titlebar);
if (titleView != null) {
ViewParent parent = titleView.getParent();
if (parent != null && (parent instanceof View)) {
View parentView = (View)parent;
parentView.setBackgroundColor(Color.RED);
}
}
ActionBar bar = getActionBar();
bar.setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.material_blue_gray1)));
I think this other dicussion will be helpful to you:
Set title background color

Categories

Resources