I have not yet found any way to solve this on stack.
I have a webview in my app. I want to be able to detect when the keyboard is active and when it is not active. It seems as if it cant detect these changes when they happon in a webview.
I want to perform actions on these different states. On iOS its really simple with observers that listens when the keyboard is active. Ref UIKeyboardWillShow/Hide.
Is there any functionality in android that does the same as these observers do in android?
Hopefully the question is well enough explained.
So I struggled with this a week or so and found a lot of material Here is my solution, I really hope that it works for you.
So in my case I have an Activity, and I call a fragment that contains the Webview so it was a lot trickier than I thought.
Basically the issue is that the fragment was missing this line:
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
So it didn't recognized the change of height inside the webview.
Anyways lets get to the code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
//mWebView.postUrl("https://www.google.com/");
final View activityRootView = view;
layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
activityRootView.getWindowVisibleDisplayFrame(r);
// This variable was created only for Debug purposes and
// to see the height change when clicking on a field inside mWebView
int screenHeight = activityRootView.getRootView().getHeight();
Log.d("onGlobalLayout", "rect: " + r.toString());
Log.d("onGlobalLayout", "screenHeight: " + screenHeight);
//The difference on the heights from bottom to top and on the root height
int heightDiff = screenHeight - (r.bottom - r.top);
Log.d("onGlobalLayout", "heightDiff: " + heightDiff);
//I suggest to put 250 on resources and retrieve from there using getResources().getInteger() to have better order
float dpx = dpToPx(getActivity(), 250);
if (previousHeightDiff != heightDiff) {
if (heightDiff > dpx) {
isSoftKeyboardPresent = true;
} else {
isSoftKeyboardPresent = false;
}
previousHeightDiff = heightDiff;
}
}
};
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
return view;
}
private static float dpToPx(Context context, float valueInDp) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
}
Remember to put on your AndroidManifest.xml a setting on the activity set to: android:windowSoftInputMode="adjustResize|stateHidden"
The variables inside the fragment are:
public boolean isSoftKeyboardPresent = false;
private int previousHeightDiff = -1;// this is used to avoid multiple assignments
private ViewTreeObserver.OnGlobalLayoutListener layoutListener = null;
Finally
#Override
public void onPause() {
super.onPause();
final View activityRootView = getActivity().findViewById(R.id.page_content);
activityRootView.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener);
}
And this should do the trick :) so what do you think?
Related
I'm setting collapingToolbarLayout scroll flags programmatically & it works fine the firs time...
-First fragment:
show an imageView in collapsingToolbar (flags: SCROLL_FLAG_SCROLL,SCROLL_FLAG_ENTER_ALWAYS, SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) WORKS FINE. then with a button go to second fragment.
-Second Fragment:
remove the imageView (Height = 0dp) and change collapsingToolbar (flags: SCROLL_FLAG_SCROLL,SCROLL_FLAG_ENTER_ALWAYS) WORKS FINE. then onBackPressed go back to first fragment.
-First fragment:
the flag "SCROLL_FLAG_ENTER_ALWAYS" don't work anymore, the collapsingToolbar always scroll to the bottom if i scroll down enough.
I'm making the changes in onDestinationChanged (NavController).
What i tried:
-Setting the flags (SCROLL_FLAG_SCROLL,SCROLL_FLAG_ENTER_ALWAYS, SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) in XML and just change the imageView visibility state Visible/Gone, same problem.
Some code:
public void onDestinationChanged(#NonNull NavController controller, #NonNull NavDestination destination, #Nullable Bundle arguments) {
appBarLayout.setExpanded(true);
if (destination.getId() == R.id.firstFragment) {
toolbarChanges();
collapsingToolbarChanges();
} else if (destination.getId() == R.id.secondFragment) {
toolbarChangesReset();
collapsingToolbarChangesReset();
}
}
private void toolbarChanges() {
ViewGroup.LayoutParams toolbarImgParams = imgToolbarBackground.getLayoutParams();
final float scale = getResources().getDisplayMetrics().density;
int height = (int) (120 * scale + 0.5f);
toolbarImgParams.height = height;
imgToolbarBackground.setLayoutParams(toolbarImgParams);
}
private void toolbarChangesReset() {
ViewGroup.LayoutParams toolbarImgParams = imgToolbarBackground.getLayoutParams();
toolbarImgParams.height = 0;
imgToolbarBackground.setLayoutParams(toolbarImgParams);
}
private void collapsingToolbarChanges() {
AppBarLayout.LayoutParams collapsingToolbarPrams = (AppBarLayout.LayoutParams) collapsingToolbar.getLayoutParams();
collapsingToolbarPrams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL |
AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS |
AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED);
collapsingToolbar.setLayoutParams(collapsingToolbarPrams);
}
private void collapsingToolbarChangesReset() {
AppBarLayout.LayoutParams collapsingToolbarPrams = (AppBarLayout.LayoutParams) collapsingToolbar.getLayoutParams();
collapsingToolbarPrams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL |
AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
collapsingToolbar.setLayoutParams(collapsingToolbarPrams);
}
I am using a glow effect which works using setMaskFilter to blur the painted area:
public static Paint createGlowPaint(Context context, #ColorRes int baseColorKey) {
float glowWidth = context.getResources().getDimension(R.dimen.analog_blur_width);
Paint paint = new Paint();
paint.setColor((Workarounds.getColor(context, baseColorKey) & 0xFFFFFF) | 0x40000000);
paint.setMaskFilter(new BlurMaskFilter(glowWidth, BlurMaskFilter.Blur.NORMAL));
paint.setStrokeWidth(glowWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
return paint;
}
This is used along with custom vector graphics to do the drawing of the blur and then the drawing of the hands of my watch face. This is all packaged up into a Drawable so that I can use it in more than one place. Or so I thought! It turns out that even though this works fine when painted directly onto the top-level canvas, when I try to use ImageView#setImageDrawable to set the exact same Drawable onto an ImageView, the filter no longer gets applied.
You can see how this sort of blur looks:
Using it with an ImageView, now you get a hard edge instead:
What is going on here?
Edit for additional info:
Code that does the drawing, i.e. is using the glow paint:
public abstract class Hands extends Drawable {
// ... lots of other cruft
#Override
public void draw(Canvas canvas) {
//todo could probably precompute more in onBoundsChange
Rect bounds = getBounds();
int centerX = bounds.centerX();
int centerY = bounds.centerY();
if (watchMode.isInteractive()) {
handGlowPath.reset();
handGlowPath.addPath(hourHand.getPath());
handGlowPath.addPath(minuteHand.getPath());
handGlowPath.addCircle(centerX, centerY, centreRadius, Path.Direction.CCW);
canvas.drawPath(handGlowPath, handGlowPaint);
}
hourHand.draw(canvas);
minuteHand.draw(canvas);
if (watchMode.isInteractive()) {
secondHand.draw(canvas);
if (hasThirds()) {
thirdHand.draw(canvas);
}
}
canvas.drawCircle(centerX, centerY, centreRadius, centrePaint.getPaint());
}
Code that puts the drawable into the ImageView:
class PreviewListViewAdapter extends BaseAdapter implements ListAdapter {
// ... other methods for the list adapter
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView instanceof ViewHolder) {
holder = (ViewHolder) convertView;
} else {
holder = new ViewHolder(getContext(), inflater.inflate(R.layout.config_list_item, parent, false));
}
// Deliberately making this a square.
LinearLayout.LayoutParams holderLayoutParams =
new LinearLayout.LayoutParams(parent.getWidth(), parent.getWidth());
LinearLayout.LayoutParams viewLayoutParams =
new LinearLayout.LayoutParams(parent.getWidth(), parent.getWidth());
if (position == 0) {
holderLayoutParams.height += 20 * getResources().getDisplayMetrics().density;
viewLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
} else if (position == getCount() - 1) {
holderLayoutParams.height += 20 * getResources().getDisplayMetrics().density;
viewLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
} else {
viewLayoutParams.gravity = Gravity.CENTER;
}
holder.setLayoutParams(holderLayoutParams);
holder.view.setLayoutParams(viewLayoutParams);
holder.image.setImageDrawable(items[position].drawable);
holder.text.setText(items[position].labelText);
return holder;
}
}
Starting from API 14, BlurMaskFilters are not supported when hardware acceleration is enabled.
To work around this, set your ImageView's layer type to software:
holder.image.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
I'm trying to change the text in TextView based on the string of the previous view with a Listview.
For some reason, it keeps returning an error. Here's my code:
public class LyricsFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/** Inflating the layout country_details_fragment_layout to the view object v */
View v = inflater.inflate(R.layout.learnlyrics, null);
/** Getting the textview object of the layout to set the details */
TextView tv = (TextView) v.findViewById(R.id.learnsong);
Bundle b = getArguments();
tv.setText("Details of " + Country.name[b.getInt("position")]);
int position = b.getInt("position");
String s = b.getInt("position");
if (s.startsWith("Pretty Hurts")) {
tv.setText("[Intro]\\nHarvey Keitel: Ms. Third Ward, your first question -- what is your aspiration in life?\\n" +
"Beyoncé: Oh, my aspiration in life would be to be happy\\n\\n[Verse 1]\\nMama said, you're a pretty girl\\n " +
"What's in your head it doesn't matter\\nBrush your hair, fix your teeth\\nWhat you wear is all that matters\\n\\n[Pre-Hook]\\nJust another stage\\nPageant the pain away\\nThis time I'm gonna take the crown\\nWithout falling down, down\\n\\n[Hook]\\nPretty hurts\\nWe shine the light on whatever's worse\\nPerfection is the disease of a nation\\nPretty hurts, pretty hurts\\nWe shine the light on whatever's worse\\nTryna fix something\\nBut you can't fix what you can't see\\nIt's my soul that needs the surgery\\n\\n[Verse 2]\\nBlonder hair, flat chest\\nTV says bigger is better\\nSouth beach, sugar free\\nVogue says thinner is better\\n\\n[Pre-Hook + Hook]\\n\\n[Bridge]\\nAin’t got no doctor or pill that can take the pain away\\nThe pain's inside\\nAnd nobody frees you from your body\\nIt's the soul, its the that needs surgery\\nIt's the soul that needs surgery\\nPlastic smiles and denial can only take you so far\\nThen you break when the fake facade leaves you in the dark\\nYou left a shattered mirror\\nAnd the shards of a beautiful girl\\n\\n[Hook]\\n\\n[Outro]\\nWhen you're alone all by yourself\\nAnd you're lying in your bed\\nReflection stares right into you\\nAre you happy with yourself?\\nIt's just a way to masquerade\\nThe illusion has been shed\\nAre you happy with yourself?\\nAre you happy with yourself?\\nYes\\n");
}
return v;
}
}
The error being:
String s = b.getInt("position");
As a new android developer, I'm not sure why? Please help.
The error is indicated by #JonasCz
you need to convert from int to String, because you can´t store an int into a String variable!
String s = String.valueOf(b.getInt("position"));
Figured this out. Changed the intent to Activity and the activity has this code:
public class SongLyrics extends Activity {
Context context;
String[] rank;
int position;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/** Setting the layout for this activity */
setContentView(R.layout.learnlyrics);
String title = getIntent().getExtras().getString("rank");
Intent i = getIntent();
position = i.getExtras().getInt("position");
rank = i.getStringArrayExtra("rank");
TextView titleTextView = (TextView) findViewById(R.id.learnsong);
// titleTextView.setText(title);
titleTextView.setText(rank[position]);
}
}
Currently using a tabbar/viewpager with fragments setup for this project. Fragment 2 contains a gridview. At app startup I'm trying to select a gridview cell by default - but no matter what I do it does not 'select'. I'm beginning to wonder if this is because at the time the selection tries to take place, the gridview is off screen (page/fragment 2 of the viewpager).
What I'm doing is after the getView method of the GridViewAdapter is initially complete (I'm comparing position to total number of possible cells to determine this) I fire a listener message to select the default cell in the GridView. I did it this way to (a) ensure that the cell I'm trying to select is non-null, and (b) I wondered if the getView method was resetting the selection somehow.
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
// * Other code that sets up the view
if (listener!=null) {
if ((list.size()-1)==position) {
Log.d(TAG, "Today position set: " + todayPosition);
listener.todayPositionFound(todayPosition);
}
} else {
Log.d(TAG, "LISTENER IS NULL");
}
return row;
}
and then...
public void todayPositionFound(final int position) {
// * ------------------------
// * On startup, select today
// * ------------------------
mCurrentlySelectedDate = DateHelper.todayAsString();
Log.d(TAG, "Todays Position Found: " + position);
View v = calendarView.getChildAt(position);
if (v!=null) {
Log.d(TAG, "V not NULL - SELECTING");
v.setSelected(true);
}
Log.d(TAG, "SELECTED? " + calendarView.getSelectedItemPosition());
}
All of this goes off without a problem, aside from the fact that the view is then NOT selected. Furthermore, when I getSelectedItemPosition it returns -1 ... even though I just 'selected' position 16. Any thoughts on this would be much appreciated. Thank you!
To get this working I used a Handler and Runnable:
public void todayPositionFound(final int position) {
Handler h = new Handler();
Runnable r =new Runnable() {
public void run() {
View v = calendarView.getChildAt(position);
if (v!=null) {
v.setSelected(true);
}
}
};
h.postDelayed(r, 500);
}
If someone has a better solution please do let me know. Thanks!
You can only update the list scroll position after the the List/GridView has been drawn. This happens a short time after onCreate() or onResume() or onCreateView() has been called.
You could try using a Global layout listener to tell you when the GridView has been drawn, for example:
GridView calendarView = (GridView)findViewById(R.id.YOUR_VIEW_ID);
ViewTreeObserver viewTreeObserver = calendarView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
this.calendarView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
View v = calendarView.getChildAt(position);
if (v!=null) {
v.setSelected(true);
}
}
});
I am extend a SupportMapFragment which contain google map here, I lay my google map on a frameLayout according to How to handle onTouch event for map in Google Map API v2?. Ondraw()method is called , but the problem is rectangle is never been showed, even when I use setWillNotDraw(false); here.
I already got the right position of the rectangle, the only problem is it is not showed. Following is my most part of code:
Add-on:When I remove the google map it works as I expect, actually the rectangle drawed on the background frameLayout, so it will be covered by the map, now the way I solve the question is add a image view as a rectangle and dynamic change its size when touch moving. I have no idea how to draw directly on the top of this viewgroup(framelayout) at the moment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
//stash reference to google map
mGoogleMap = getMap();
//show the location
mGoogleMap.setMyLocationEnabled(true);
mTouchView = new TouchableWrapper(getActivity());
mTouchView.addView(view);
return mTouchView;
}
private class TouchableWrapper extends FrameLayout {
//box is used to illustrate the drawing box, the painter is used to set the color of the box.
private Box mCurrentBox = null;
private Paint mBoxPaint;
public TouchableWrapper(Context context) {
super(context);
mBoxPaint = new Paint();
mBoxPaint.setColor(0xffff0000);
// mBoxPaint.setStrokeMiter(20);
mBoxPaint.setStrokeWidth(8);
this.setWillNotDraw(false);
setWillNotDraw(false);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// decide when the child should hold the motion event.
return true; //always let the relative layout to hold the motion event.
}
#Override
public boolean onTouchEvent(MotionEvent event) {
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mMapIsTouched = true;
mCurrentBox = new Box(curr);//original code for draw Rectangle.
Log.d(TAG, "action.down box is"+mCurrentBox);
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "action_move");
if (mCurrentBox != null) {
mCurrentBox.setmCurrent(curr);
Log.d(TAG, "action.move box is"+mCurrentBox);
this.invalidate(); //TODO
}
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "action_up");
mMapIsTouched = false;
mCurrentBox = null;
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
float left = 0;
float right = 0;
float top = 0;
float bottom = 0;
if (mCurrentBox!=null) {
left = Math.min(mCurrentBox.getmOrigin().x, mCurrentBox.getCurrent().x) ;
right = Math.max(mCurrentBox.getmOrigin().x, mCurrentBox.getCurrent().x) ;
top = Math.min(mCurrentBox.getmOrigin().y, mCurrentBox.getCurrent().y) ;
bottom = Math.max(mCurrentBox.getmOrigin().y, mCurrentBox.getCurrent().y) ;
canvas.drawRect(left, top, right, bottom, mBoxPaint);
}
Log.i(TAG, "value is"+left+right+top+bottom);
}
You could wrap the Map in a FrameLayout containing also a Drawable (your rectangle). In that way, once you find where the rectangle shall be, you could hide and show it without having to redraw it.
You can see here for an example.