When i do :
LayoutParams lp = getLayoutParams(view);
lp.x = absoluteX;
lp.y = absoluteY;
this.mWindowManager.updateViewLayout(view, lp);
Then i have one linear animation from the position where the view is to the new position absoluteX/absoluteY. how to move the view without any animation ?
I try to set lp.windowAnimations = 0 but it's change nothing :(
any idea how i can do ?
If it's not possible to avoid the animation, is their any way to know the actual position of the view? maybe i can make it invisible still the real position is not absoluteX/absoluteY
NOTE:
looking the source code of android, i saw in the file WindowManager.java this entry :
/**
* Never animate position changes of the window.
*
* {#hide} */
public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
/**
* Control flags that are private to the platform.
* #hide
*/
public int privateFlags;
it's look like to be what i need, but i don't know how to access and set such flag :( any idea ?
try to disable the layout animation:
android:animateLayoutChanges="false"
val lp = WindowManager.LayoutParams(
width,
height,
this.windowManagerType(),
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
PixelFormat.TRANSPARENT
)
val className = "android.view.WindowManager\$LayoutParams"
try {
val layoutParamsClass = Class.forName(className)
val privateFlags = layoutParamsClass.getField("privateFlags")
val noAnim = layoutParamsClass.getField("PRIVATE_FLAG_NO_MOVE_ANIMATION")
var privateFlagsValue = privateFlags.getInt(lp)
val noAnimFlag = noAnim.getInt(lp)
privateFlagsValue = privateFlagsValue or noAnimFlag
privateFlags.setInt(lp, privateFlagsValue)
} catch (e: Exception) {
Log.e("EXCEPT", "EXCEPTION: ${e.localizedMessage}")
}
Related
I have implemented a custom keyboard, that is drawn in an ImageView. Now I want to make it floating, it should be placeable anywhere.
The position, width and height is set in the following way:
private void setKeyboardDimensionAndPosition(int widthPI, int heightPI, int xDP, int yDP) {
WindowManager.LayoutParams windowParams = getWindow().getWindow().getAttributes();
windowParams.x = (int) (xDP * GUIManager.getDensity());
windowParams.y = (int) (yDP * GUIManager.getDensity());
windowParams.width = widthPI;
windowParams.height = heightPI;
(new Handler(Looper.getMainLooper())).post(() -> {
getWindow().getWindow().setAttributes(windowParams);
});
}
But the keyboard is always sticked to the text box - it is always below it! It would even be fine when the text box is covered by the keyboard, it should be totally free!
So I need to set up 2 things:
Icon for helper text in TextInputLayout
Center it to TOP, since helper text might be multiline
It might look something like this:
Is there easy of doing working around this, without making my own TextInputLayout?
Solving this melts down to following 2 possible solutions:
First is to change the Rect of the Drawable itself by setting the bounds (coordinates X and Y), drawback of this solution is whenever layout is redrawn (orientation change etc.), this has to be done again.
findViewById<AppCompatTextView>(R.id.textinput_helper_text)?.run {
compoundDrawables.first()?.run {
setBounds(
bounds.left,
bounds.top - this#with.height.div(2),
bounds.right,
bounds.bottom - this#with.height.div(2)
)
bottom = this#with.height.div(2)
}
}
Second solution is to add new ImageView in front of the helper TextView and add padding to it, so it's pushed up.
It's not a one liner, but it can be formed into easy to use method as such:
fun TextInputLayout.setStartHelperTextIcon(
#DrawableRes drawableRes: Int,
paddingPx: Int
) {
with(findViewById<AppCompatTextView>(R.id.textinput_helper_text)) {
(this?.parent?.parent as? LinearLayout)?.run {
if(findViewById<ImageView>(R.id.text_input_layout_helper_icon) == null) {
addView(
ImageView(context).apply {
id = R.id.text_input_layout_helper_icon
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setPadding(
paddingPx,
0,
paddingPx,
paddingPx + this#with.height.div(2)
)
},
0
)
}
}
}
}
in the project, I tried adding a View object dynamically in a RelativeLayout
ImageView card = new ImageView(this);
card.setImageResource(R.drawable.card);
RelativeLayout.LayoutParams layoutParams =
new RelativeLayout.LayoutParams(225, 315);
layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams.rightMargin = 40;
parent.addView(card, layoutParams);
after a while, when I tried to get the location of this View, no method works.
As I found in Studio's debug view, the View's layoutparams and all its attrs like mLeft... got the value 0.
So how could I solve the problem? thanks a lot.
It might be because you tried to get position before onLayout has happened.
You might try something like this:
ViewTreeObserver vto=view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
#Override public void onGlobalLayout(){
int [] location = new int[2];
view.getLocationOnScreen(location);
x = location[0];
y = location[1];
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
Also check how to get view's position in coordinates?
You can use these code to get the location:
int loc[]=new int[2];
card.getLocationOnScreen(loc);
int x=loc[0];
int y=loc[1];
If you want to get the location, you have to wait for the View to complete the measurement.
Simple and best way to find a location of view on screen.
view.post(() -> {
xPosition = view.getX());
yPosition = view.getY();
};
view.post() is used because of that if you try to get view location onCreate() method without using post then you get (0,0) because of that view takes few millisecond to inflate on the screen.
Using the Data Binding Library in Android to get location of a view.
private fun getViewLocation(){
val loginBinding = ActivityLoginBinding.inflate(layoutInflater)
val locationX = loginBinding.forgotPasswordBtn.x
val locationY = loginBinding.forgotPasswordBtn.y
// locationX and locationY are returned as Float values
}
I have a method:
public class MarginConverter {
int top = 0;
int bottom = 0;
int right = 0;
int left = 0;
public MarginConverter(String val){
top = bottom = right = left = Integer.parseInt(val);
}
public LayoutParams getLayoutParamsFromView(View view){
LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
int height, width;
if (layoutParams == null) {
height = LayoutParams.WRAP_CONTENT;
width = LayoutParams.MATCH_PARENT;
layoutParams = new LayoutParams(width, height);
layoutParams.setMargins(left, top, right, bottom);
} else {
layoutParams.setMargins(left, top, right, bottom);
}
return layoutParams;
}
}
Note: Variables left, top, right, bottom are instance variables initialized on constructor call.
I want to test this method. I have mocked view.getLayoutParams() method. Method setMargin(int, int, int,int) is not setting margin parameters as mentioned and I am getting all parameters of LayoutParams(leftMargin, rightMargin, topMargin, bottomMargin) 0.0 in test though it's working correctly in code. What should I do so that I will get values as expected.
Please help me ! Any help will be appreciated..
This is my testCase code:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ViewGroup.LayoutParams.class})
public class MarginConverterTest {
#Mock
private View view = mock(TextView.class);
private int left = 0;
private int right = 0;
private int top = 0;
private int bottom = 0;
#Before
public void setUp() throws Exception {
when(view.getLayoutParams()).thenReturn(null);
}
/**
* tests output for margin conversion when one argument is given
*/
#Test
public void test_OneArgumentMarginConversion(){
left =top = right = bottom = 5;
MarginConverter marginConverter = new marginConverter("5");
LayoutParams params = marginConverter.getLayoutParamsFromView(view);
Object[] marginArray = new Object[4];
marginArray[0] = params.leftMargin;
marginArray[1] = params.topMargin;
marginArray[2] = params.rightMargin;
marginArray[3] = params.bottomMargin;
Assert.assertArrayEquals(new Object[]{left,top,right,bottom}, marginArray);
}
}
You seem to be confused about concept of mocking. If you mock something, it means its a dummy ( not real object ) and all of its method calls are fake.
If you wish margins to be non-zero , LayoutParams(leftMargin, rightMargin, topMargin, bottomMargin) , don't mock view or setMargin method.
If your view object is not real ( mocked , dummy etc ), your layoutParams is a fake one too ( means unknown values ) unless you are returning an actual layoutParams by using some mocking framework.
In simple terms, if you wish to set margins by calling setMargins , don't mock setMargins method call and make sure that layoutParams is not fake one.
Also, in your case if view is mocked, condition if (layoutParams == null) will never be satisfied as layoutParams will be a non - null fake object.
Solution - use an appropriate mocking method on call view.getLayoutParams() to return an appropriate real layoutParams object or null to satisfy both branches of code , if as well as else.
Hope it helps and let me know if I misunderstood your question.
As I was mocking view, view was not actually created which leads to this problem. I used RobolectricTestRunner instead of PowerMockRunner and created view using shaddow application context. And it worked..!
Now my testcode is:
#RunWith(RobolectricTestRunner.class)
public class MarginConverterTest {
private int left = 0;
private int right = 0;
private int top = 0;
private int bottom = 0;
View view;
#Before
public void setUp() throws Exception {
view = new TextView(Robolectric.getShadowApplication().getApplicationContext());
}
/**
* tests output for margin conversion when one argument is given
*/
#Test
public void test_OneArgumentMarginConversion(){
left =top = right = bottom = 5;
MarginConverter marginConverter = new marginConverter("5");
LayoutParams params = marginConverter.getLayoutParamsFromView(view);
Object[] marginArray = new Object[4];
marginArray[0] = params.leftMargin;
marginArray[1] = params.topMargin;
marginArray[2] = params.rightMargin;
marginArray[3] = params.bottomMargin;
Assert.assertArrayEquals(new Object[]{left,top,right,bottom}, marginArray);
}
}
Does anyone know if you can change the compass position within the map?
All I can find is how to enable or disable it. But I need to move it down a bit so my overlay is not obstructing it.
Use GoogleMap.setPadding() method:
https://developers.google.com/maps/documentation/android/map#map_padding
#Override
public void onMapReady(GoogleMap map) {
try {
final ViewGroup parent = (ViewGroup) mMapView.findViewWithTag("GoogleMapMyLocationButton").getParent();
parent.post(new Runnable() {
#Override
public void run() {
try {
Resources r = getResources();
//convert our dp margin into pixels
int marginPixels = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, r.getDisplayMetrics());
// Get the map compass view
View mapCompass = parent.getChildAt(4);
// create layoutParams, giving it our wanted width and height(important, by default the width is "match parent")
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(mapCompass.getHeight(),mapCompass.getHeight());
// position on top right
rlp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
rlp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
rlp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0);
//give compass margin
rlp.setMargins(marginPixels, marginPixels, marginPixels, marginPixels);
mapCompass.setLayoutParams(rlp);
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
I know it's been a long time that the question was asked , but I feld on this issue a few days ago I fixed the problem like this :
try {
assert mapFragment.getView() != null;
final ViewGroup parent = (ViewGroup) mapFragment.getView().findViewWithTag("GoogleMapMyLocationButton").getParent();
parent.post(new Runnable() {
#Override
public void run() {
try {
for (int i = 0, n = parent.getChildCount(); i < n; i++) {
View view = parent.getChildAt(i);
RelativeLayout.LayoutParams rlp = (RelativeLayout.LayoutParams) view.getLayoutParams();
// position on right bottom
rlp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
rlp.addRule(RelativeLayout.ALIGN_PARENT_TOP,0);
rlp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
rlp.rightMargin = rlp.leftMargin;
rlp.bottomMargin = 25;
view.requestLayout();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
in this exemple i put the compass in the corner right. You need to be sure that your mapFragment is created when you do this, i suggest you run your code in the method "onMapReady" of your MapFragment.
As I know you can't change compass position but you can disable it and build yours private one.
See example here
Padding-based solutions work for small offsets, but as far as I know there's no API exposed that allows us to robustly change the horizontal "gravity" of the compass from left to right like the stock Google Maps app does:
Note that if you applied a large amount of left padding to move the compass to the right in your own app, the Google logo in the bottom left would also be shifted over to the right.
On 2.0 map api.
// change compass position
if (mapView != null &&
mapView.findViewById(Integer.parseInt("1")) != null) {
// Get the view
View locationCompass = ((View) mapView.findViewById(Integer.parseInt("1")).getParent()).findViewById(Integer.parseInt("5"));
// and next place it, on bottom right (as Google Maps app)
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)
locationCompass.getLayoutParams();
// position on right bottom
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
layoutParams.setMargins(0, 160,30, 0); // 160 la truc y , 30 la truc x
}
Based on the answer of #Vignon (https://stackoverflow.com/a/38266867/2350644), here is a Kotlin code snippet to position the compass icon to the top right of the screen.
You can also add custom margins (in this example the compass image has a marginTop of 50dp).
mapFragment?.view?.let { mapView ->
mapView.findViewWithTag<View>("GoogleMapMyLocationButton").parent?.let { parent ->
val vg: ViewGroup = parent as ViewGroup
vg.post {
val mapCompass: View = parent.getChildAt(4)
val rlp = RelativeLayout.LayoutParams(mapCompass.height, mapCompass.height)
rlp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0)
rlp.addRule(RelativeLayout.ALIGN_PARENT_TOP)
rlp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0)
val topMargin = (50 * Resources.getSystem().displayMetrics.density).toInt()
rlp.setMargins(0, topMargin, 0, 0)
mapCompass.layoutParams = rlp
}
}
}
Better use following snippet:
mapView.findViewWithTag<View>("GoogleMapCompass")?.let { compass ->
compass.post {
val topMargin = compass.marginTop
val rlp = RelativeLayout.LayoutParams(compass.height, compass.height)
rlp.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0)
rlp.addRule(RelativeLayout.ALIGN_PARENT_TOP)
rlp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0)
val endMargin = (4 * Resources.getSystem().displayMetrics.density).toInt()
rlp.setMargins(0, topMargin, endMargin, 0)
compass.layoutParams = rlp
}
}
This way you don't have to use "GoogleMapMyLocationButton" and go over the parent. Iterating to the parent doesn't bring any benefit and could break more easy in the future, when google provides an update of the mapView.
Within that code snipped you directly access the compass, that's the element you need here.
There is my solution.
First of all, I need this Kotlin extension to get all subview for any view
fun View.getAllChildren(): List<View> {
val result = ArrayList<View>()
if (this !is ViewGroup) {
result.add(this)
} else {
for (index in 0 until this.childCount) {
val child = this.getChildAt(index)
result.addAll(child.getAllChildren())
}
}
return result
}
After that, I just retrieve the compass by looking for it with his content description (Use Layout Inspector to retrieve it just in cas if content description change in the future).
When you have your view, change his position like this:
binding.mapView
.getAllChildren()
.firstOrNull { it.contentDescription == "Compass" }
?.let { it.y = it.y + 400 }
Following #Vignon herre's a suggestion for position the compass in lower left corner (in kotlin):
mapFragment.view?.let { mapView->
mapView.findViewWithTag<View>("GoogleMapMyLocationButton").parent?.let { parent->
val vg: ViewGroup = parent as ViewGroup
vg.post {
val mapCompass :View = parent.getChildAt(4)
val rlp = RelativeLayout.LayoutParams(mapCompass.height, mapCompass.height)
rlp.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
rlp.addRule(RelativeLayout.ALIGN_PARENT_TOP,0)
rlp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,0)
rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
val bottomMargin = (40 * Resources.getSystem().displayMetrics.density).toInt()
val leftMargin = (5 * Resources.getSystem().displayMetrics.density).toInt()
rlp.setMargins(leftMargin,0,0,bottomMargin)
mapCompass.layoutParams = rlp
}
}
}