I have quite a complex problem to do with Android views. I am creating a paint application, and I have two views: a transparent background view and the pixel art board.
For both views, I want the height and width to be calculated off of the distance between view A and B:
Instead of calculating the distance between these two views, I simply 'constraint' a view in the middle like so, and then extract its height by using its measuredHeight property (and yes, you could also calculate the distance between view A and B in the code, but my problem still remains when I try that):
Now, here's the XML code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/fragment_background_color_daynight"
tools:context=".activities.canvas.CanvasActivity">
<View
android:id="#+id/activityCanvas_topView"
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="#color/fragment_background_color_daynight"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.therealbluepandabear.pixapencil.customviews.colorswitcherview.ColorSwitcherView
android:id="#+id/activityCanvas_colorSwitcherView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:isPrimarySelected="false"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_colorPickerRecyclerView"
app:layout_constraintEnd_toEndOf="#+id/activityCanvas_topView"
app:layout_constraintTop_toTopOf="#+id/activityCanvas_colorPickerRecyclerView"
app:primaryColor="#android:color/holo_green_dark"
app:secondaryColor="#color/black" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/activityCanvas_colorPickerRecyclerView"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_topView"
app:layout_constraintEnd_toStartOf="#+id/activityCanvas_colorSwitcherView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/activityCanvas_primaryFragmentHost"
tools:listitem="#layout/color_picker_layout" />
<FrameLayout
android:id="#+id/activityCanvas_distanceContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_tabLayout"
app:layout_constraintEnd_toEndOf="#+id/activityCanvas_primaryFragmentHost"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/activityCanvas_topView" />
<com.google.android.material.card.MaterialCardView
android:id="#+id/fragmentOuterCanvas_canvasFragmentHostCardViewParent"
style="#style/activityCanvas_canvasFragmentHostCardViewParent_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="1dp"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_tabLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/activityCanvas_topView">
<!-- At runtime, the width and height will be calculated -->
<com.therealbluepandabear.pixapencil.customviews.transparentbackgroundview.TransparentBackgroundView
android:id="#+id/activityCanvas_transparentBackgroundView"
android:layout_width="0dp"
android:layout_height="0dp" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.tabs.TabLayout
android:id="#+id/activityCanvas_tabLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:tabStripEnabled="false"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_viewPager2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_tools_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_filters_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_color_palettes_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_brushes_str" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/activityCanvas_viewPager2"
android:layout_width="0dp"
android:layout_height="110dp"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_primaryFragmentHost"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/activityCanvas_coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
android:id="#+id/activityCanvas_primaryFragmentHost"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Of course, when it comes to calculating, I thought it would be best practise to utilize AndroidX's OneShotPreDrawListener, like so:
OneShotPreDrawListener.add(binding.root) {
binding.activityCanvasTransparentBackgroundView!!.setViewWidth(binding.activityCanvasDistanceContainer!!.measuredHeight)
binding.activityCanvasTransparentBackgroundView!!.setViewHeight(binding.activityCanvasDistanceContainer!!.measuredHeight)
}
Now, for some reason, the result looks like so:
Why is this the case!
I did some debugging, and when I log the height of view C, I get the following:
This is wrong. So, as an experiment, I added a GlobalLayoutListener to detect when exactly the view's constraints get resolved:
binding.activityCanvasDistanceContainer?.viewTreeObserver?.addOnGlobalLayoutListener( object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
Log.d("M_LOG", binding.activityCanvasDistanceContainer?.measuredHeight.toString())
}
})
Result:
So, after the first couple of times it's 438, until after the 4th/5th time it shoots up to 1000.
I am really confused why this is happening, as I want to run the event when the constraints have been fully resolved and calculated, but using OneShotPreDrawListener (or any other alternative) is just running the event when the view has been drawn, but not yet when it has been positioned properly.
I am confused what to do. How can I run an event when the view's constraints have been fully calculated?
Edit for Chetichamp:
I have debugged this and I think I can reproduce this error and tell you in what scenario it occurs and in which scenario it does not.
Basically in my app, prior to activity creation, I have a fragment called NewProjectFragment, which looks like so:
Here's the code for when the 'Done' button is pressed:
binding.fragmentNewCanvasDoneButton.setOnClickListener {
checkForTitleError()
checkForWidthError()
checkForHeightError()
if (!invalidTitle && !invalidWidth && !invalidHeight) {
try {
val title =
binding.fragmentNewCanvasProjectTitleTextInputEditText.text.toString()
val widthValue: Int =
binding.fragmentNewCanvasWidthTextInputEditText.text.toString().toInt()
val heightValue: Int =
binding.fragmentNewCanvasHeightTextInputEditText.text.toString().toInt()
if (widthValue + heightValue >= 2000 && (requireActivity() as MainActivity).showLargeCanvasSizeWarning) {
val frameLayout: FrameLayout =
this#setOnClickListeners.activity?.layoutInflater?.inflate(
R.layout.dont_show_large_canvas_warning_again_checkbox,
requireView().findViewById(android.R.id.content),
false
)
as FrameLayout
val checkBox = frameLayout.getChildAt(0) as MaterialCheckBox
requireActivity().showDialog(
getString(R.string.generic_warning_in_code_str),
getString(R.string.dialog_large_canvas_warning_text_in_code_str),
getString(R.string.dialog_large_canvas_warning_positive_button_text_in_code_str),
{ _, _ ->
if (checkBox.isChecked) {
(requireActivity() as MainActivity).showLargeCanvasSizeWarning =
false
with((requireActivity() as MainActivity).sharedPreferenceObject.edit()) {
putBoolean(
StringConstants.Identifiers.SHARED_PREFERENCE_SHOW_LARGE_CANVAS_SIZE_WARNING_IDENTIFIER,
(requireActivity() as MainActivity).showLargeCanvasSizeWarning
)
apply()
}
}
caller.onDoneButtonPressed(
title,
widthValue,
heightValue,
paramSpotLightInProgress
)
},
getString(R.string.dialog_unsaved_changes_negative_button_text_in_code_str),
{ _, _ ->
},
frameLayout
)
} else {
caller.onDoneButtonPressed(
title,
widthValue,
heightValue,
paramSpotLightInProgress
)
}
} catch (exception: Exception) {
HapticFeedbackWrapper.performHapticFeedback(binding.fragmentNewCanvasDoneButton)
}
} else {
HapticFeedbackWrapper.performHapticFeedback(binding.fragmentNewCanvasDoneButton)
}
}
As you can see, it has a listener, so the code for the listener, which is in MainActivity, is like so (maybe this is causing the issue? and I just don't need a listener like this? I don't know if you think this is why I get the issue):
fun MainActivity.extendedOnDoneButtonPressed(projectTitle: String, width: Int, height: Int, spotLightInProgress: Boolean) {
startActivity(
Intent(this, CanvasActivity::class.java)
.putExtra(StringConstants.Extras.PROJECT_TITLE_EXTRA, projectTitle)
.putExtra(StringConstants.Extras.WIDTH_EXTRA, width)
.putExtra(StringConstants.Extras.HEIGHT_EXTRA, height)
.putExtra(StringConstants.Extras.SPOTLIGHT_IN_PROGRESS_EXTRA, spotLightInProgress)
)
}
Now, what I come to the conclusion is that all of this extra work is causing a delay, because when you simply tap on a pre-existing project, we can see that the intent is much simpler:
fun MainActivity.extendedOnCreationTapped(param: PixelArt) {
startActivity(
Intent(this, CanvasActivity::class.java)
.putExtra(StringConstants.Extras.INDEX_EXTRA, pixelArtData.indexOf(param))
.putExtra(StringConstants.Extras.PROJECT_TITLE_EXTRA, param.title))
}
With a simple intent like so, the problem doesn't get reproduced, and it sizes properly.
What I relaized is that the work done in NewProject fragment is causing a delay, and when I simply scrap out the work and perfom a simple intent, the problem is 'fixed'. I don't know how to fix this but hopefully it can help with finding a solution.
Debugging even further
When I debug the issue even further, I notice something strange. The measuredHeight of the root layout jumps up by one thousand:
This is not observed when the creation is tapped with the simple intent. I am lost for words as to how strange this bug is, I've never seen this in my life.
There is something going on with your code that is touching your views that is causing the multiple layouts. Without that code being posted, it is impossible to say what.
To help you with debugging, set the one shot to the following:
OneShotPreDrawListener.add(binding.root) {
Log.d("Applog", "OneShot")
binding.activityCanvasTransparentBackgroundView.layoutParams.width =
binding.activityCanvasDistanceContainer.measuredWidth
binding.activityCanvasTransparentBackgroundView.layoutParams.height =
binding.activityCanvasDistanceContainer.measuredHeight
binding.activityCanvasTransparentBackgroundView.requestLayout()
}
In my test, when placing this in the onCreateView() function of a fragment, "OneShot" is logged just once and the size of the view is adjusted as you say it should be. (btw, requestLayout() was required in this test.)
I would take a look at any other code that touches your views to see if something else is triggering the multiple calls: maybe setWidth() or setHeight().
I will add that I have noticed in the past that ConstraintLayout will call the global layout listener multiple times. Usually, I would say, the listener is removed on the first call, so this behavior would not be noticed.
Instead of the global layout listener or the predraw listener, you could try a layout listener:
binding.root.doOnLayout {
// Your code here
}
or
binding.root.doOnNextLayout {
// Your code here
}
doOnNextLayout is guaranteed to be called only once, while doOnLayout may be called multiple times.
Wow guys.
I finally found a solution, after 12 hours of nonstop debugging, almost going clinically insane, and logging and destroying my codebase.
I realized that the keyboard from the NewProjectFragment was cutting the view, causing the bug. Holy moly... I am ecstatic that I finally found why this is happening but at the same time shocked how I didn't discover this before!
The solution was to add the following to the activity:
<activity
android:name="com.therealbluepandabear.pixapencil.activities.canvas.CanvasActivity"
android:windowSoftInputMode="stateHidden" // this
android:hardwareAccelerated="true"
android:exported="false" />
Prior to this, the windowSoftInputMode was adjustResize -- causing the bug.
This question already has answers here:
editText get text kotlin
(8 answers)
Closed last year.
I have the problem when I want to register a new E-Mail to my firebase store.
I have that button in my activity_register.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RegisterActivity">
<EditText
android:id="#+id/editTextEmailAddress"
android:layout_width="237dp"
android:layout_height="49dp"
android:layout_marginTop="104dp"
android:layout_marginBottom="12dp"
android:background="#drawable/edittextbackground"
android:ems="10"
android:hint="Email"
android:inputType="textEmailAddress"
android:padding="10dp"
android:textColor="#color/black"
app:layout_constraintBottom_toTopOf="#+id/editTextPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.502"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
....
After that I want to use it to register to the firebase store:
fun register(view: View){
val email: String=R.id.editTextEmailAddress.toString()
val password=R.id.editTextPassword.javaClass.toString()
auth.createUserWithEmailAndPassword(email,password).addOnCompleteListener { task ->
if(task.isSuccessful){
val intent= Intent(this,MainActivity::class.java)
startActivity(intent)
finish()
}
}.addOnFailureListener { exception ->
Toast.makeText(applicationContext,exception.localizedMessage,Toast.LENGTH_LONG).show()
}
}
If I take a look at the debugger there I can see the following:
Screenshot from Debugger
It looks like it is an integer. How can I fix that Problem?
I tried to solve it with bindings, but there the inflate is not possible.
Same Problem with the password one row below.
Problem is that you are taking the reference to the view instead, you should do like this
val emailField = findViewById(R.id.editTextEmailAddress)
val passwordField = findViewById(R.id.editTextPassword)
Now access the text
val email = emailField.text.toString()
val password = passwordField.text.toString()
I'm struggling with very common problem, I think.
I've created a button in xml file and tagged it with ID. Then I wanted to make onClickListener in MainActivity.kt. But when I'm typing button's ID, it's marked red and it seems like Android Studio doesn't recognise it. I've tried cleaning and rebuilding project, but the problem still exist. Invalidate Caches/Restart didn't help as well.
Here's XML Code
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/backgroundColor"
android:fadingEdge="vertical"
android:fadingEdgeLength="80dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity"
tools:viewBindingIgnore="true">
<Button
android:id="#+id/btnDateBicker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:backgroundTint="#3D446C"
android:text="Select Date"
android:textSize="25sp"
android:textStyle="bold" />
</LinearLayout>
And here's kotlin code
package com.example.ageinminutes
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mybtn = findViewById<btnDateBicker>()
}
}
If you simply want to fetch by id, use findViewById method.
val myBtn: Button = findViewById(R.id.btnDateBicker)
or
val myBtn = findViewById<Button>(R.id.btnDateBicker)
Another way to play with views in the kotlin file:
Try with synthetic binding, like just start writing few words of XML id in your kotlin file and android studio will attach that view for you. [Deprecated after Android 11]
Try with view binding or data binding, you just have to enable these settings in build.gradle file. [More Robust way]
Try this:
val mybtn = findViewById<Button>(R.id.btnDateBicker)
I'm making a race timing app in Android Studio in kotlin that calculates a bunch of useful information off of split times. At the beginning of the race, bib 1 starts at 0, but bib 2 might start 15s later. I want the user to see each racer in a RecyclerView with a chronometer counting up (from for example -15) until that racer's start (0), and continue counting up as the race is going.
What I've noticed is the Chronometer widget counts zero twice when it is starting from negative numbers and goes into positive numbers. Is there a way to have it only count zero once?
Below is code to set the chronometer inside of each cardview to the time until they start, and then starting them.
p0.athleteChrono.base = (SystemClock.elapsedRealtime() + racer.startTime *1000)
p0.athleteChrono.start()
Again, it sets itself correctly to, for example -00:10, starts counting up, but definitely displays -00:01, 00:00, 00:00, 00:01 before continuing up
Here is a sample main activity that just has a chronometer set to -3
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnStart.setOnClickListener {
chronometer.base = SystemClock.elapsedRealtime() + 3000
chronometer.start()
}
}
}
and the sample layout file activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="#+id/chronometer"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="Chronometer"></TextView>
<Chronometer
android:id="#+id/chronometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:padding="10dp"></Chronometer>
<Button
android:id="#+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#+id/chronometer"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="#color/colorPrimary"
android:text="Start"></Button>
</androidx.constraintlayout.widget.ConstraintLayout>
I am new to android development, and have been trying to use the beginner's tutorial as my starting point for developing a simple app. There is one screen, with an image, a row of four buttons, a textbox for the user to enter a pin, and a textview to display results.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/linear_layout_1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="#+id/imageView1"
android:contentDescription="#string/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/skytrek"
/>
<LinearLayout
android:id="#+id/linear_layout_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="#string/button_today"
android:onClick="today_click" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="#string/button_tomorrow"
android:onClick="tomorrow_click" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="#string/button_this_week"
android:onClick="this_week_click" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="#string/button_next_week"
android:onClick="next_week_click" />
</LinearLayout>
<EditText android:id="#+id/editText1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:hint="#string/edit_message" />
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/init_message"/>
</LinearLayout>
I have code to deliver the results I want based on the button pressed - all fine. But then I wanted the user to enter a PIN as well as pressing a button, so I added the EditText control. This threw the following error:
E/AndroidRuntime(28578): Caused by: java.lang.ClassCastException: android.widget.TextView cannot be cast to android.widget.EditText
My Java class:
private String content = null;
private TextView textView1;
private EditText editText1;
public void today_click(View view) {
getPage("today");
}
public void tomorrow_click(View view) {
getPage("tomorrow");
}
public void this_week_click(View view) {
getPage("thisweek");
}
public void next_week_click(View view) {
getPage("nextweek");
}
public void getPage(String strParam) {
editText1 = (EditText) findViewById(R.id.editText1);
String message = editText1.getText().toString();
if (message.equals("4567")) {
content = "PIN recognised";
} else {
content = "PIN not recognised";
}
textView1 = (TextView) findViewById(R.id.textView1);
textView1.setText(content);
}
I thought I had done something silly, using the name of a TextView instead of an EditText control, but I can't find it if I have.
The error is being thrown at the line
getPage("thisweek");
I didn't understand how this line involved views of any sort, but of course the function heading
this_week_click(View view)
does, and when I changed the order of the TextView and the EditText in the XML file (so that the TextView comes first), the error disappeared. It is as if the "view" being passed is not the button, but the nearest widget to the button. I have read
existence of parameter (View view)
but it only seems to confirm my (mis)understanding that a button should be passed as the view parameter. I have also tried cleaning the project, and building a completely new project. What on earth is causing the casting error?
If you're using Eclipse, go to the menu voice "Project" and select "Clean"
Sometimes Eclipse has some problem with ids, by cleaning the project you regenerate them..
Everytime if you make any changes in xml or any interchange of position of views in xml or change of id's,you need to clean build your project.If not you will get this exception.
Hello I have tested your code, your code is fine. Please clean your project from
select your project then click on Project and then Clean and also check the Build Automatically . Your auto generated class R is not generated properly.
Try cleaning your project and run it again..
Eclipse -> Project menu ->Clean
It is because eclipse gets confused when we play around in the xml file ;)