I am trying to create functions that instanciate some views in a Linear Layout
private lateinit var layout: LinearLayout
var par = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)
override fun onCreate(savedInstanceState: Bundle?) {
//The init stuff with binding
layout = binding.root.findViewById(R.id.gamelayout)
lpar.setMargins(10,10,10,10)
test()
}
private fun instanciateText(s:String)
{
val tv = TextView(this)
tv.text = s
tv.layoutParams = par
layout.addView(tv)
}
fun instanciateImage()
{
val iv = ImageView(this)
iv.setImageResource(R.drawable.start1)
iv.layoutParams = par
layout.addView(iv)
}
fun test(){
instanciateText("Text 1 ")
instanciateImage()
instanciateText("Text 2 ")
}
It works fine with text, but whith images, the views are so far from each other, and i cannot find why
If a stretchable ImageView is added, it is to be expanded to the maximum extent so the LinearLayout gets 100%-filled with children. It's right behavior. If you'd like to avoid that, specify ImageView.adjustViewBounds = true.
fun instanciateImage()
{
val iv = ImageView(this)
iv.setImageResource(R.drawable.start1)
iv.layoutParams = par
iv.adjustViewBounds = true
layout.addView(iv)
}
Related
I'm building my first game in Android Studio. Right now, dots fall from the top of the screen down to the bottom. For some reason, in Layout Inspector the view of each dot is the entire screen even though the dots are comparatively small. This negatively affects the game since when a user presses anywhere on the screen, it deletes the most recently created dot rather than the one pressed. I want to get the dot's view to match the size of the actual dots without effecting other functionality.
Dot.kt
class Dot(context: Context, attrs: AttributeSet?, private var dotColor: Int, private var xPos: Int, private var yPos: Int) : View(context, attrs) {
private var isMatching: Boolean = false
private var dotIsPressed: Boolean = false
private var isDestroyed: Boolean = false
private lateinit var mHandler: Handler
private lateinit var runnable: Runnable
init {
this.isPressed = false
this.isDestroyed = false
mHandler = Handler()
runnable = object : Runnable {
override fun run() {
moveDown()
invalidate()
mHandler.postDelayed(this, 20)
}
}
val random = Random()
xPos = random.nextInt(context.resources.displayMetrics.widthPixels)
startFalling()
startDrawing()
}
// other methods
fun getDotColor() = dotColor
fun getXPos() = xPos
fun getYPos() = yPos
fun isMatching() = isMatching
fun setMatching(matching: Boolean) {
this.isMatching = matching
}
fun dotIsPressed() = dotIsPressed
override fun setPressed(pressed: Boolean) {
this.dotIsPressed = pressed
}
fun isDestroyed() = isDestroyed
fun setDestroyed(destroyed: Boolean) {
this.isDestroyed = destroyed
}
fun moveDown() {
// code to move the dot down the screen
yPos += 10
}
fun checkCollision(line: Line) {
// check if dot is colliding with line
// if yes, check if dot is matching or not
// update the dot state accordingly
}
fun startFalling() {
mHandler.post(runnable)
}
fun startDrawing() {
mHandler.postDelayed(object : Runnable {
override fun run() {
invalidate()
mHandler.postDelayed(this, 500)
}
}, 500)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (!isDestroyed) {
val paint = Paint().apply {
color = dotColor
}
canvas?.drawCircle(xPos.toFloat(), yPos.toFloat(), 30f, paint)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var score = 0
private lateinit var scoreCounter: TextView
private val dots = mutableListOf<Dot>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
createLine(Color.RED, 5000)
scoreCounter = TextView(this)
scoreCounter.text = score.toString()
scoreCounter.setTextColor(Color.WHITE)
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.setBackgroundColor(Color.BLACK)
val params = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
scoreCounter.layoutParams = params
layout.addView(scoreCounter)
val dotColors = intArrayOf(Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW)
val random = Random()
val handler = Handler()
val runnable = object : Runnable {
override fun run() {
val dotColor = dotColors[random.nextInt(dotColors.size)]
createAndAddDot(0, 0, dotColor)
handler.postDelayed(this, 500)
}
}
handler.post(runnable)
}
fun updateScore(increment: Int) {
score += increment
scoreCounter.text = score.toString()
}
fun createAndAddDot(x: Int, y: Int, color: Int) {
Log.d("Dot", "createAndAddDot called")
val dot = Dot(this, null, color, x, y)
val layout = findViewById<ConstraintLayout>(R.id.layout)
layout.addView(dot)
dots.add(dot)
dot.setOnTouchListener { view, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
val dotToRemove = dots.find { it == view }
dotToRemove?.let {
layout.removeView(it)
dots.remove(it)
updateScore(1)
view.performClick()
}
}
true
}
}
fun createLine(color: Int, interval: Int) {
Log.d("Line", "createLine called")
val line = Line(color, interval)
val lineView = Line.LineView(this, null, line)
val layout = findViewById<ConstraintLayout>(R.id.layout)
if (layout == null) {
throw IllegalStateException("Layout not found")
}
layout.addView(lineView)
val params = ConstraintLayout.LayoutParams(2000, 350)
lineView.layoutParams = params
params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID
params.bottomMargin = (0.1 * layout.height).toInt()
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Your view here -->
<View
android:id="#+id/view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- Guideline set to 10% from the bottom -->
<androidx.constraintlayout.widget.Guideline
android:id="#+id/bottom_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.1" />
</androidx.constraintlayout.widget.ConstraintLayout>
I tried changing the view size with
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val diameter = 40 // or any other desired diameter for the dots setMeasuredDimension(diameter, diameter) }
That made the view size a square stuck in the top left corner. As I played around with it, I could only get dots to show in that small window in the top corner rather than moving down the screen from different starting x-positions
Your custom view isn't a dot, it's a large display area that draws a dot somewhere inside it and animates its position. In onDraw you're drawing a circle at xPos (a random point on the screen width via displayMetrics.widthPixels) and yPos (an increasing value which moves the dot down the view).
There are two typical approaches to things like this:
use simple views like ImageViews. Let the containing Activity or Fragment add them to a container and control their position, maybe using the View Animation system. Handle player interaction by giving them click listeners and let the view system work out what's been clicked.
create a custom view that acts as the game area. Let that custom view control the game state (what dots exist, where they currently are) and draw that state in onDraw. Handle touch events on the view, and work out if those touches coincide with a dot (by comparing to the current game state).
What you're doing is sort of a combination of the two with none of the advantages that either approach gives on its own. You have multiple equally-sized "game field" views stacked on top of each other, so any clicks will be consumed by the top one, because you're clicking the entire view itself. And because your custom view fills the whole area, you can't move it around with basic view properties to control where the dot is - you have to write the logic to draw the view and animate its contents.
You could implement some code that handles the clicks and decides whether the view consumes it (because it intersects a dot) or passes it on to the next view in the stack, but that's a lot of work and you still have all your logic split between the Activity/Fragment and the custom view itself.
I think it would be way easier to just pick one approach - either use ImageViews sized to the dot you want and let the view system handle the interaction, or make a view that runs the game internally. Personally I'd go with the latter (you'll find it a lot easier to handle dots going out of bounds, get better performance, more control over the look and interaction etc, no need to cancel Runnables) but it's up to you!
I created a scrollView programmaticaly that contains 20 views each with an image and a text.
I have two questions :
1 - is the id assignment correct and is my setOnClickListener correct?
2 - By which method onClick can I know which view of the scrollView the user has clicked?
See my code below
private var integerList: MutableList<Int>? = mutableListOf()
private var cellNo: MutableList<String>? = mutableListOf()
private var sv_mvmtChoosed = ""
private fun showSpinner() {
/* SCROllL VIEW */
var linearLayout: LinearLayout? = null
linearLayout = findViewById(R.id.linear1)
val layoutInflater = LayoutInflater.from(this)
var randIndex = 0
for (posIndex in 0..19) {
val rand = Random()
randIndex = rand.nextInt(20)
while (integerList!!.contains(randIndex)) {
randIndex = rand.nextInt(20)
}
integerList!!.add(randIndex)
// Create the view...
val view: View = layoutInflater.inflate(R.layout.scroll_bckgrnd, linearLayout, false)
// give it an id
view.id = generateViewId()
view.setOnClickListener(this)
cellNo!!.add(view.id.toString())
println(cellNo)
//... then populate it with image and text
val iv = view.findViewById<ImageView>(R.id.iv)
iv.setImageResource(sv_photoImage[randIndex])
val tv = view.findViewById<TextView>(R.id.tv)
tv.text = sv_photoName[randIndex]
linearLayout?.addView(view)
}
// which view the user did select?
fun onClick(view: View?) {
when (view!!.id) {
??? -> doSomething
}
}
}
Any idea to get me back on track will be welcome.
Its probably better to make a new OnClickListener for every view.
view.setOnClickListener(this)
needs to be this
view.setOnClickListener {
// do something
}
or
view.setOnClickListener(createOnClickListner())
fun createOnClickListner():View.OnClickListener{
return object :View.OnClickListener{
override fun onClick(view : View) {
//do something with the view that was clicked
}
}
}
Thanks a lot avalerio.
I finally found a solution as follow :
I replaced :
// give it an id
view.id = generateViewId()
view.setOnClickListener(this)
cellNo!!.add(view.id.toString())
println(cellNo)
with :
// give it an id
view.id = posIndex
view.setOnClickListener(this)
then I did this :
// the onClickListener for my 20 images/text
override fun onClick(view: View?) {
when (view!!.id) {
// Now de position clicked on the ScrollView
in 0..19 -> didHeSucceeded(view!!.id)
}
}
And use the result:
private fun didHeSucceeded(scrllPos: Int) {
// TODO: close de scrollView, how ? :-)
spinnerChoice = nameOfTechScrollVw[scrllPos]
succes = (!allreadyChoosedArray.contains(spinnerChoice)) && (currentArray.contains(spinnerChoice
))
if (succes) {
...
...
}
It works perfectly
I am trying to use a secondary display on an android device (POS terminal).
In the activities onCreate, I have :
val mediaRouter = getSystemService(Context.MEDIA_ROUTER_SERVICE) as MediaRouter
val route = mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO)
if (route != null) {
val display = route.presentationDisplay
if (display != null) {
val presentation = TestPresentation(this, display)
presentation.show()
}
}
which correctly checks for a presentation display. Then I have my presentation class :
class TestPresentation(context: Context, display: Display):Presentation(context, display) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val linearLayout = LinearLayout(context)
val params = LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
linearLayout.layoutParams = params
val composeView = ComposeView(context).apply {
layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
setContent {
Text(text = "Hello")
}
}
linearLayout.addView(composeView)
setContentView(linearLayout)
}
At runtime I get an exception :
java.lang.IllegalStateException: ViewTreeLifecycleOwner not set for this ComposeView. If you are adding this ComposeView to an AppCompatActivity, make sure you are using AppCompat version 1.3+. If you are adding this ComposeView to a Fragment, make sure you are using Fragment version 1.3+. For other cases, manually set owners on this view by using `ViewTreeLifecycleOwner.set()` and `ViewTreeSavedStateRegistryOwner.set()`.
How do I use ViewTreeLifecycleOwner.set() and ViewTreeSavedStateRegistryOwner.set() in this case ?
I've been searching for a solution but had no luck.
An example would be great.
I'm currently using a Table Layout and programatically adding rows and buttons. However, when I add the views a lot of them go off-screen. Is there a way to programatically set the size to the portion of the screen.
I have decent experience with Android, but new to Kotlin.
Here is where I add the views
private fun setupTable () {
for(i in 0 until this.rowSize) {
val row = TableRow(context)
row.layoutParams
row.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
for(j in 0 until this.columnSize) {
val button = Button(context)
button.apply {
layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT,
TableRow.LayoutParams.WRAP_CONTENT)
text = "R $i C $j"
}
row.addView(button)
}
wordLayout?.addView(row)
}
}
Here is the picture for reference. Here I want a 10x10 table and to fit all the buttons inside the TableLayout.
Try the following code:
package net.androidly.androidlylayouts
class MainActivity : AppCompatActivity() {
val ROWS = 10
val COLUMNS = 5
val tableLayout by lazy { TableLayout(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView.text = "ROWS : $ROWS COLUMNS: $COLUMNS"
val lp = TableLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
tableLayout.apply {
layoutParams = lp
isShrinkAllColumns = true
}
createTable(ROWS, COLUMNS)
}
fun createTable(rows: Int, cols: Int) {
for (i in 0 until rows) {
val row = TableRow(this)
row.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
for (j in 0 until cols) {
val button = Button(this)
button.apply {
layoutParams = TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT,
TableRow.LayoutParams.WRAP_CONTENT)
text = "R $i C $j"
}
row.addView(button)
}
tableLayout.addView(row)
}
linearLayout.addView(tableLayout)
}
}
Hope this helps.
It's awesome to be learning ConstraintLayout and Kotlin. I have a constraintLayout popupView, which is the parent view of a TextView titleLabel. I'd like popupView height to adjust to the content of its child titleLabel TextView. titleLabel height can vary due to different text strings used.
Both titleLabel and popupView are set to WRAP_CONTENT for the height layout params, but the popupView isn't getting rendered. Adding a fixed height constraint to popupView's constraintSet will render it, but a fixed height will not work when titleLabel height changes.
Any ideas how to get it working? Here's what I have
open class PopupActivity(): AppCompatActivity() {
public var message:String = "This is a message string for the label"
val titleLabel: TextView by lazy {
val label = TextView(this)
label.gravity = Gravity.CENTER
label.setTextSize(Constants.FontSizePopupTitle)
return#lazy label
}
val popupView: ConstraintLayout by lazy {
val view = ConstraintLayout(this)
view.setBackgroundColor(Color.primary())
return#lazy view
}
val view: ConstraintLayout by lazy {
val v = ConstraintLayout(this)
return#lazy v
}
#SuppressLint("ResourceType")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
view.id = 1
popupView.id = 2
titleLabel.id = 5
var margin = 2 * Constants.SpacingStandard.toInt()
view.addView(popupView)
popupView.addView(titleLabel)
titleLabel.layoutParams = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.MATCH_CONSTRAINT,
ConstraintLayout.LayoutParams.WRAP_CONTENT)
titleLabel.text = message
popupView.layoutParams = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.MATCH_CONSTRAINT,
ConstraintLayout.LayoutParams.WRAP_CONTENT)
val popupConstraintSet = ConstraintSet()
popupConstraintSet.connect(popupView.id, START, view.id, START, margin)
popupConstraintSet.connect(popupView.id, END, view.id, END, margin)
popupConstraintSet.centerHorizontally(popupView.id, view.id)
popupConstraintSet.centerVertically(popupView.id, view.id)
view.setConstraintSet(popupConstraintSet)
setContentView(view)
}
}
You can fix this by switching the order of the view#id in the connect functions
popupConstraintSet.connect(view.id, START, popupView.id, START, margin)
popupConstraintSet.connect(view.id, END, popupView.id, END, margin)
popupConstraintSet.centerHorizontally(view.id, popupView.id)
popupConstraintSet.centerVertically(view.id, popupView.id)
OR set the ConstraintSet to the popUpView. The field already has the correct name ^^
popUpView.setConstraintSet(popupConstraintSet)
Here's what I ended up doing:
remove popupView.layoutParams; WRAP_CONTENT for height seems to be ignored...
add constrainHeight with WRAP_CONTENT to constraintSet
onCreate is now this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
view.id = 1
popupView.id = 2
titleLabel.id = 5
popupView.addView(titleLabel)
view.addView(popupView)
titleLabel.layoutParams = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.MATCH_CONSTRAINT,
ConstraintLayout.LayoutParams.WRAP_CONTENT)
titleLabel.text = message
var margin = 2 * Constants.SpacingStandard.toInt()
val popupConstraintSet = ConstraintSet()
popupConstraintSet.connect(popupView.id, START, view.id, START, margin)
popupConstraintSet.connect(popupView.id, END, view.id, END, margin)
popupConstraintSet.constrainHeight(popupView.id, ConstraintSet.WRAP_CONTENT)
popupConstraintSet.centerHorizontally(popupView.id, view.id)
popupConstraintSet.centerVertically(popupView.id, view.id)
view.setConstraintSet(popupConstraintSet)
setContentView(view)
}
}