How to change Position of Imageview with button in Kotlin - android

So Im supernew to Kotlin so im trying to create a simple game where if I press a button an Imageview will shift left or right. Below I tried using ObjectAnimator and it works. When I press the button it shifts to the right but it only does that only once on runtime. As it stands now I have one button programmed but I hope to have 4 directional buttons where I can move an Imageview around the screen. So How can I keep changing the position of an imageview when the app is running ?
Thank You!
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//val picture = findViewById<ImageView>(R.id.SpongeBob)
val position_button = findViewById<Button>(R.id.position_button)
position_button.setOnClickListener()
{
ObjectAnimator.ofFloat(SpongeBob, "TranslationX", 100f).apply {
duration = 200
start()
}
}
}
}

You can use picture.animate().setDuration(200).translationXBy(100f), be sure to type translationXBy and not translationX! You can also use "translationXBy" in your ObjectAnimator or use a variable instead of 100f.
What you are doing right now is moving the picture to x: 100 all the time, but what you really want is to move it by 100.
I made a small demo for you here.

Related

Using binding to Linear Layout and Setting an Image but ontop of the pervious View (Image)

I'm having an issue with this for two days now... I have a binding and an image to pop when clicking a button.
I want that every time the button is pressed another image will pop either next to the pervious one, or, if there's no space in the linear layout, that it will pop ontop of the previous one.
The idea is to get a card from a card deck and put it ot the hand of the player.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPlayTableBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
var image = findViewById<ImageView>(R.id.fight_deck)
var i: Int = 1
val deck = Deck()
var deckListOfCards: MutableList<Card> = deck.shuffledDeckOfCard()
val deck_clickable_top = findViewById<ImageButton>(R.id.deck_button)
val imageView = ImageView(this)
val Width = convertDpToPixel(135f, this)
val Height = convertDpToPixel(190f, this)
imageView.layoutParams = LinearLayout.LayoutParams(Width.toInt(), Height.toInt())
var imgResId: Int = 0
deck_clickable_top.setOnClickListener {
if (deckListOfCards.size != 0) {
imgResId = deckListOfCards.get(0).getCardFace()
i += 1
deck.removeCardFromDeck(deckListOfCards.get(0))
}
if (deckListOfCards.size == 0) {
deck_clickable_top.visibility = View.INVISIBLE
deck_clickable_top.layoutParams.height = 0
}
imageView.setImageResource(imgResId)
}
binding.bottomLinearLayout.addView(imageView)
}
I can't figure out how to do it...
Well, LinearLayout doesn't seem like a good choice for this, but you can try something along these lines:
// step 1 - this is the linear layout you'll use as the container
val containerBinding = CustomLinearLayoutContainerBinding.inflate(inflater, container, false)
// step 2 - on some click event you can create a new binding, this should contain you image
val componentBinding = CustomComponentLayoutBinding.inflate(inflater, container, false).apply {
// set the image drawable here, text, any other data in here
}
// step 3 - the root layout of that binding is the linear layout
containerBinding.root.addView(componentBinding.root)
// the following 2 methods of LinearLayout can be very handy
removeAllViews() // to remove all previously added children
invalidate() // to re-render the linear layout
But please note that everything you want to do with those views after you added them will need manual changes and again, this doesn't feel like you should go for it. However, I found this link, check out this implementation, feels like this suits your requirements better.

How to keep imageView going offscreen [Kotlin]

So I am new to Kotlin and I am trying to make a super simple app. All it does is when I click Right button it goes right same with left button. The problem is that when I click either button (e.g right button) I can click it until the image goes completely offscreen. So how can I implement a code that once it hits the edge off the screen it stops moving ?
My code
package com.example.change_position_circle
import android.animation.ObjectAnimator
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//val picture = findViewById<ImageView>(R.id.SpongeBob)
val right_button = findViewById<Button>(R.id.right)
val left_button = findViewById<Button>(R.id.left)
right_button.setOnClickListener()
{
//ObjectAnimator.ofFloat(SpongeBob, "x", 100)
SpongeBob.animate().setDuration(90).translationXBy(100f)
}
left_button.setOnClickListener()
{
//ObjectAnimator.ofFloat(SpongeBob, "translationXBy", 100f).apply {
//duration = 200
// start()
SpongeBob.animate().setDuration(90).translationXBy(-100f)
//}
}
}
}
Thank you for your help
Welcome to Kotlin!
So yeah, you've got your Spongebob animating, now you need some logic to control that animation. The problem here is you don't always want that full animation to happen, right? If he's too close to the edge of the screen, you want the button to only move him as far as that invisible wall (and if he's right against it, that means no movement at all).
The animation and drawing systems don't put any restrictions on where you can put a View, so it's up to you to handle that yourself. You basically need to do this when a button is clicked:
get the Spongebob's position coordinates (it's the X you really care about right now)
work out the position of the edge you care about (the View's coordinates describe where the top left corner is, so if you're looking at the right edge, you need that X coordinate + the width of the View)
work out the X coordinate of the edge of the screen (or parent layout, whatever you want the Spongebob to be contained within)
if the distance between the Spongebob edge and the screen edge is less than your normal animation movement, you need to change it to that remaining distance
you'll also want to work out the appropriate duration too, if he's moving half the usual distance the animation should take half as long
that's a lot to work on, and there are a few ways to do it, but here's one approach just using the screen edges as the bounds
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import kotlin.math.abs
import kotlin.math.min
private const val MOVE_DISTANCE = 100
private const val MOVE_TIME = 90
class MainActivity : AppCompatActivity() {
private var screenWidth = 0
private lateinit var spongeBob : View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.spongebob)
// store this when the Activity is created, if the device is rotated the Activity
// will be recreated and this method gets run again
screenWidth = applicationContext.resources.displayMetrics.widthPixels
//val picture = findViewById<ImageView>(R.id.SpongeBob)
val right_button = findViewById<Button>(R.id.right)
val left_button = findViewById<Button>(R.id.left)
spongeBob = findViewById(R.id.spongeBob)
right_button.setOnClickListener()
{
// two possible values - the distance to the edge, and the normal amount we move
// we want the smaller of the two (i.e. always move the normal amount, unless
// the edge is closer than that
val distance = min(distanceToEdge(left = false), MOVE_DISTANCE)
moveSpongeBob(distance)
}
left_button.setOnClickListener()
{
val distance = min(distanceToEdge(left = true), MOVE_DISTANCE)
// we're moving left so we need to use a negative distance
moveSpongeBob (-distance)
}
}
private fun distanceToEdge(left: Boolean): Int {
// Get the Spongebob's top-left position - the call is a method on the View class,
// I'm assuming SpongeBob is a View, and you need to pass an array in because
// that's just how it works for whatever reason...
val location = IntArray(2)
spongeBob.getLocationOnScreen(location)
val x = location[0]
// I'm just using the View.getWidth() call here (Kotlin style) but I don't know
// what the SpongeBob class is, so you'll need to handle this
// You could set this once, like when we get the screen width, but width will be 0 until
// the View is laid out - so you can't do it in #onCreate, #onViewCreated should work
val spongeBobWidth = spongeBob.width
// the left edge is just the x position, however far that is from zero
return if (left) x
// the right edge is the x position plus the width of the bob
else screenWidth - (x + spongeBobWidth)
}
// Actually move the view, by the given distance (negative values to move left)
private fun moveSpongeBob(distance: Int) {
// work out how much this distance relates to our standard move amount, so we can
// adjust the time by the same proportion - converting to float so we don't get
// integer division (where it's rounded to a whole number)
val fraction = distance.toFloat() / MOVE_DISTANCE
// distance can be negative (i.e. moving left) so we need to use the abs function
// to make the duration a postitive number
val duration = abs(MOVE_TIME * fraction).toLong()
spongeBob.animate().setDuration(duration).translationXBy(distance.toFloat())
}
}
There's nicer stuff you can do (and SpongeBob should be called spongeBob and be a View) but that's the basics. This article on the coordinate system might help you out too.

How do I detect transition end in navigation graph?

I have following transition in my app, but the animation to the new destination lags because of extensive loading into recyclerview and other graphic elements.
I have tried to use postDelayed when loading elements on the recyclerview, but on some devices you feel you are waiting after transition and others it take longer time to do the transition (device dependent). So I landed on 700ms, but it is not good for all...
Is there a way (i.e. callbacks) where I can detect when the transition has actually confirmed ended.
My code is farly simple, when clicking a button in one fragment I call this:
private fun onDetailClick(stationId: String, itemView: View)
{
(itemView.context as MainActivity).bottom_navigation.visibility = View.GONE
StationDetailRepository.readStationCurrentPrices2(stationId)
val direction = StationListFragmentDirections.actionStationListFragmentToStationDetailFragment(stationId)
try {
Log.i(TAG,"trns: before navigate")
itemView.findNavController()?.navigate(direction)
Log.i(TAG,"trns: after navigate")
}catch(iae: IllegalArgumentException)
{
iae.printStackTrace()
}
catch (ise: IllegalStateException)
{
ise.printStackTrace()
}
}
Simply, how can I detect that this navigation actually has ended, and there set a viewModel share which I can observe in the other Fragment, so can trigger the loading of elements in a sane manner ?
RG

Weighted buttons in vertical linear layout cutting off text if button text line counts vary

TL;DR: Here's the gist of everything that I can think of that's relevant to the issue I'm facing: [GIST LINK]
And here's a picture of the problem
I'm trying to set up a number of buttons that will all grow to the same size as each other by equal weighting in a vertically oriented LinearLayout container.
The problem I'm facing surfaces when the text on these buttons cause a different number of lines per button.
Let's say n is the lowest line count for the buttons and m is the highest line count; any descenders in the text of buttons with line count m are cut off. Refer to the words "qshowing my clipping problem" in the linked screengrab, where all descenders are cut off.
How can I go about fixing this? The clipping gets much worse if I introduce android:lineSpacingExtra to the button style.
If it's relevant, my minimum API is set to 21
I've fixed this using RxJava to set the height programmatically to the correct maximum so that no clipping occurs. If there is a better solution I'll be glad to see it, but this is what is working for me for now:
class MyActivity {
// ...
private val compositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.my_activity)
// ...
val container: LinearLayout = findViewById(R.id.container)
val numBtns = getNumBtnsToAdd()
val btnList: MutableList<Button> = mutableListOf()
val margin10 = dpToPx(10f).toInt()
val countDown = CountDownLatch(numBtns)
val desiredLp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0).apply {
gravity = Gravity.CENTER
setMargins(margin10, margin10, margin10, margin10)
}
// Completable will be run once per subscriber and emit a success or error
val layoutCompletable = Completable.fromAction {
countDown.await()
for (btn in btnList) btn.layoutParams = desiredLp
}.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
compositeDisposable.add(
layoutCompletable.subscribe({
Log.d("MyActivity", "Set LayoutParams on all buttons.")
}, Throwable::printStackTrace)
)
for (i in 0 until numBtns) {
val btn = Button(this, null, 0, R.style.button_style).apply {
layoutParams = LinearLayout.LayoutParams(desiredLp).apply { height = LinearLayout.LayoutParams.WRAP_CONTENTS }
text = if (i == 0) "Button${i+1} with short text"
else "Button${i+1} with text that will span multiple lines showing my clipping problem"
setOnClickListener { doSomething() }
}
val listener = object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
countDown.countDown()
val height = btn.height
if (height > desiredLp.height) desiredLp.height = height
btn.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
btn.viewTreeObserver.addOnGlobalLayoutListener(listener)
btnList.add(btn)
container.addView(btn)
}
// ...
}
override fun finish() {
compositeDisposable.clear()
super.finish()
}
// ...
}
My guess, the main cause is buttons have fixed size. More preciously, you use LinearLayout to share available room between buttons via weight attribute. You can see the single line button height is same with 2-lines button height. So 2-lines buttons are forced to clip the text.
According your XML file you want enable the vertical scroll when there is no more room. In this case you don't need to use weight attribute. Just buttons under each other with margins.

Flow textview around image

I've spent hours looking for answer and have really no idea how to solve it. So let's get down to business:
There is an image and a TextView and I need to flow the TextView around the ImageView like this:
First possible solution woult be to use https://github.com/deano2390/FlowTextView but it's not extending TextView so this library is not suitable for me for number of reasons.
Second solution would be to use LeadingMarginSpan.LeadingMarginSpan2 span but it affects on each paragraph for each n lines inside the text (like in this answer -> How to layout text to flow around an image), so I get smth like this:
But I wanted to set margin only for first n lines! Then I decided to implement LeadingMarginSpan.Standart and create a counter and increment it in getLeadingMargin(first: Boolean): Int function invocation. When the counter reach the desirable value, the function returns 0 as a margin width. And there is a fail again! Instead of filling the TextView lines, the text just moved left and didn't spread to the end of the view!
UPD: Yes, I've used onGlobalLayoutListener in here
Well, googling for another solution I found this answer https://stackoverflow.com/a/27064368/7218592
Ok, I've done everything as described and implemented the code:
//set left margin of desirable width
val params: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.leftMargin = holder.imageContainerHeight!!
params.addRule(RelativeLayout.BELOW, holder.mNumberAndTimeInfo!!.id)
holder.mCommentTextView!!.layoutParams = params
if (holder.commentTextViewOnGlobalLayoutListener != null)
holder.mCommentTextView!!.viewTreeObserver.removeOnGlobalLayoutListener(
holder.commentTextViewOnGlobalLayoutListener)
//add onGlobalLayoutListener
holder.mCommentTextView!!.viewTreeObserver.addOnGlobalLayoutListener(
if (holder.commentTextViewOnGlobalLayoutListener != null)
holder.commentTextViewOnGlobalLayoutListener
else CommentTextViewOnGlobalLayoutListener(holder,
SpannableString(HtmlCompat.fromHtml(
mView.getActivity(), commentDocument.html(), 0,
null, SpanTagHandlerCompat(mView.getActivity())))))`
My OnGlobalLayoutListener looks like this: `
class CommentTextViewOnGlobalLayoutListener(
val holder: CommentAndFilesListViewViewHolder, val commentSpannable: Spannable) :
ViewTreeObserver.OnGlobalLayoutListener {
val LOG_TAG: String = CommentTextViewOnGlobalLayoutListener::class.java.simpleName
override fun onGlobalLayout() {
holder.mCommentTextView!!.viewTreeObserver.removeGlobalOnLayoutListener(this)
//when textview layout is drawn, get the line end to spanify only the needed text
val charCount = holder.mCommentTextView!!.layout.getLineEnd(Math.min(
holder.mCommentTextView!!.layout.lineCount - 1,
CommentLeadingMarginSpan.computeLinesToBeSpanned(holder)))
if (charCount <= commentSpannable.length) {
commentSpannable.setSpan(CommentLeadingMarginSpan(holder),
0, charCount, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
//set the left margin back to zero
(holder.mCommentTextView!!.layoutParams as RelativeLayout.LayoutParams).leftMargin = 0
holder.mCommentTextView!!.text = commentSpannable
}
}
`
Well, it works. But how terrible it works! As I'm using view holder pattern I have to hold a variable to the listener and remove if it is not been called and successfully removed because onGlobalLayout function wasn't called in time! And it is called too late, so you need to wait about 300 ms and then watch all the "reconstruction" of the TextView and it looks disgustingly!
So, my question is:
How to make margins for first n lines in TextView, before it's been drawn on UI?
This is more a suggestion that will only work with a little trial and error
This code uses a multi line Edit Text
btnPrint.setOnClickListener {
val str = """
One
Two
Three
Now click Action Button Custom SB
""".trimIndent()
etNews.setText(str)
}
Play with the One Two values indent and trimIndent has other properties available

Categories

Resources