I use ViewPropertyAnimator for creating alpha animations. Both withStartAction() and withEndAction() work great, alpha values changed, but they changed in no time, looks like there is no animation, just changing values. Where is a mistake?
private fun run(millisecondsPerPixel: Long) { //millisPerPixel=30
pixelsToCenter = (_endHeight - _startHeight - emptyPlaceOffset) / 2.toLong() // 15
val duration = millisecondsPerPixel * pixelsToCenter // 450
val animator = if (opening) mainLayout!!.animate() else secondaryLayout!!.animate()
animator.alpha(0.0F).setDuration(duration)
.withStartAction {
changing = true
val resize = if (opening) ResizeAnimation(this#CustomToolbar, height, _endHeight) else
ResizeAnimation(this#CustomToolbar, height, _startHeight)
resize.duration = millisecondsPerPixel * (_endHeight - _startHeight)
startAnimation(resize) // this one works like a charm
}
.withEndAction {
val backAnimator = if (opening) {
mainLayout!!.visibility = View.GONE
secondaryLayout!!.animate()
} else {
secondaryLayout!!.visibility = View.GONE
mainLayout!!.animate()
}
backAnimator.alpha(1.0F).setDuration(duration)
.setStartDelay(millisecondsPerPixel * emptyPlaceOffset)
.withStartAction {
if (opening) secondaryLayout!!.visibility = View.VISIBLE else mainLayout!!.visibility = View.VISIBLE
}
.withEndAction { changing = false }
}
}
Related
I have a custom flip transformation in a ViewPager2, I've disabled user input so it can only change pages programmatically, however, the speed of the transition is way too fast.
The behavior I need is with 2 fragments, the first one loads and after a few milliseconds I trigger the transition programmatically, I see the animation and the 2nd fragment, that's it.
Here is the pager related code:
viewPager.apply {
adapter = ViewPagerAdapter(this#HostFragment)
setPageTransformer(VerticalFlipTransformation())
isUserInputEnabled = false
}
At some point I trigger the transition like this:
viewPager.currentItem = 1
Here is my adapter:
private class ViewPagerAdapter(fragment: Fragment) :
FragmentStateAdapter(fragment) {
override fun getItemCount() = 2
override fun createFragment(position: Int) = if (position == 0) {
Fragment1()
} else {
Fragment2()
}
}
Finally here is the transformation I'm using:
class VerticalFlipTransformation : ViewPager2.PageTransformer {
override fun transformPage(page: View, position: Float) {
page.translationX = -position * page.width
page.cameraDistance = 20000f
if (position < 0.5 && position > -0.5) {
page.visibility = VISIBLE
} else {
page.visibility = GONE
}
when {
position < -1 -> {
page.alpha = 0f
}
position <= 0 -> {
page.alpha = 1f
page.rotationX = 180 * (1 - abs(position) + 1)
}
position <= 1 -> {
page.alpha = 1f
page.rotationX = -180 * (1 - abs(position) + 1)
}
else -> {
page.alpha = 0f
}
}
}
}
I need to slow down the transition, any ideas? Thanks in advance!
I'm building a calculator app and in it there's a ScrollView, used to show and switch the buttons for operators and units whenever the user switches between modes.
The problem is that I didn't want to create a XML layout for each mode, so I thought of adding those buttons programmatically (which now, for me, seems pretty hard to accomplish). Here's the code that's supposed to add them:
// For each text in texts (which represents the modes), a Button is created and, if its id (represented by the index of its respective text in the list) is greater than 0, it is positioned below the previous one.
fun ScrollView.add(context: Context, input: EditText, texts: List<String>) {
removeAllViews()
val container = ConstraintLayout(context).apply {
layoutParams = ConstraintLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
topToTop = this#add.top
startToStart = this#add.left
endToEnd = this#add.right
}
}
val buttons: MutableList<Button> = mutableListOf()
texts.forEach { text ->
val button = Button(context)
val originalWidth = 60
val width = originalWidth.toDimension(context, COMPLEX_UNIT_DIP)
val originalHeight = 60
val height = originalHeight.toDimension(context, COMPLEX_UNIT_DIP)
with(button) {
layoutParams = ConstraintLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
id = texts.indexOf(text)
val previous = try { buttons[id - 1] } catch (exception: Exception) { this }
setWidth(width)
setHeight(height)
with(layoutParams as ConstraintLayout.LayoutParams) {
if (id == 0)
topToTop = this#add.top
else if (id > 0) {
layoutParams = RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
(layoutParams as RelativeLayout.LayoutParams).addRule(BELOW, previous.id)
}
}
left = this#add.left
right = this#add.right
button.text = text
isAllCaps = false
textSize = 25f
while (text().contains(System.getProperty("line.separator").toString())) textSize -= 5
setOnClickListener { input.input((it as Button).text()) }
setBackgroundResource(0)
container.addView(this)
buttons.add(this)
val buttonAdded = "[Dynamic List] Button $id added as \"$text\""
println(if (id == 0) "$buttonAdded." else "$buttonAdded. The button was positioned below \"${previous.text}\" (${previous.id}).")
}
}
addView(container)
}
And here's the code I implemented using the method above:
// Each calculator mode is represented by an Int within the list.
val modes = listOf("calculator" to 1, "length" to 2, "temperature" to 3, "time" to 4)
fun mode(context: Context, button: Button, input: EditText, view: ScrollView) {
var counter = 0
val operators = listOf("+", "-", times.toString(), division.toString())
val length = with(context) { listOf(getString(R.string.light_year), context.getString(R.string.kilometer), context.getString(R.string.hectometer), context.getString(R.string.decameter), context.getString(R.string.mile), context.getString(R.string.meter), context.getString(R.string.centimeter), context.getString(R.string.millimeter), context.getString(R.string.micrometer)) }
val temperature = with(context) { listOf(getString(R.string.celsius), context.getString(R.string.fahrenheit), context.getString(R.string.kevin), context.getString(R.string.rankine), context.getString(R.string.reaumur)) }
val time = with(context) { listOf(getString(R.string.year), context.getString(R.string.month), context.getString(R.string.day), context.getString(R.string.hour), context.getString(R.string.minute), context.getString(R.string.second), context.getString(R.string.millisecond)) }
with(button) {
setOnClickListener {
if (counter < modes.size - 1) counter++ else counter = 0
with(view) {
val mode: Pair<String, Int>? = modes[counter]
when (mode?.first) {
"calculator" -> add(context, input, operators)
"length" -> add(context, input, length)
"temperature" -> add(context, input, temperature)
"time" -> add(context, input, time)
}
text = with(context) {
with(resources) { getString(identify(context, mode?.first, "string")) }
}
}
}
}
}
Well, the problem is when I run it, the UI ends up looking like this, with all the buttons positioned at the same place:
I have a Fragment I'm using ExoPlayer in with its own PlayerView, hosted by an Activity that has a popup 'dialog' view with another PlayerView that I intend to share an ExoPlayerInstance with. Both PlayerViews are declared in the layout XML.
The Player itself is declared in the Fragment.
When the Activity's PlayerView borrows the Fragment's, the Player object is transferred just fine. However, when the Activity returns the Player object to the Fragment, for some reason the PlayerView in the Fragment is null.
I know I could just do a null check on the PlayerView, but that might not solve the root cause - the reason why findViewById of a Fragment suddenly returns null.
So, how does that happen, and how do I work to solve, or work around, it?
EDIT:
The code I use to borrow/return the Player object in the Fragment:
fun borrowPlayer(): SimpleExoPlayer {
vw_exo_player.player = null
vw_exo_player.fadeInvisible(100)
return player
}
fun returnPlayer() {
if(vw_exo_player != null) {
vw_exo_player.player = player
vw_exo_player.fadeVisible(100)
}
}
The fragment's XML is basically as follows:
<android.support.constraint.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:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<com.google.android.exoplayer2.ui.PlayerView
android:id="#+id/vw_exo_player"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:resize_mode="zoom"
app:surface_type="texture_view"
app:use_controller="false"
tools:visibility="invisible" />
...
</android.support.constraint.ConstraintLayout>
As seen here, the Fragment's layout XML has that view ID'd vw_exo_player.
The two functions above are called in the Activity:
fun showPopupVideo(anchor: PopupVideoAnchor) {
if (popupVideoHolders[anchor]?.isOn == true) return
if (anchor == PopupVideoAnchor.WEB && (popupVideoHolders[PopupVideoAnchor.QNA]?.isOn == true || popupVideoHolders[PopupVideoAnchor.POLL]?.isOn == true)) {
return
}
Trace.i("Show Popup : $anchor")
if (pv_popup.player == null) {
pv_popup.player = findFragment(GameAVStreamFragment::class.java)?.borrowPlayer()
}
val x: Float = popupVideoHolders[anchor]?.x ?: 0F
val y: Float = popupVideoHolders[anchor]?.y ?: 0F
val w: Int = popupVideoHolders[anchor]?.w ?: 0
val h: Int = ((w.f / videoSize.w.f) * videoSize.h.f).i
val b: Float = if (popupVideoHolders[anchor]?.isBordered == true) 0.95F else 1F
val layoutParam = lay_popup_video.layoutParams
for (popupVideoHolder in popupVideoHolders) {
if (popupVideoHolder.value.isOn) {
loadAnimation(R.anim.fade_out).apply {
duration = 150
onEnd {
lay_popup_video.clipScale = 1F
lay_popup_video.x = x
lay_popup_video.y = y
lay_popup_video.layoutParams.width = w
lay_popup_video.layoutParams.height = h
lay_popup_video.requestLayout()
lay_popup_video_border.clipScale = b
popupVideoHolders[anchor]?.isOn = true
lay_popup_video.clearAnimation()
lay_popup_video.startAnimation(loadAnimation(R.anim.fade_in).apply { duration = 150 })
}
lay_popup_video.clearAnimation()
lay_popup_video.startAnimation(this)
}
return
}
}
popupVideoHolders[anchor]?.isOn = true
currPopupVideoAnim?.cancel()
currPopupVideoAnim = AnimatorSet().apply {
duration = 350L
onStart {
lay_popup_video.visible()
Trace.d("Start Popup Video Animation")
}
playTogether(
ObjectAnimator.ofFloat(lay_popup_video, "clipScale", lay_popup_video.clipScale, 1F),
ObjectAnimator.ofFloat(lay_popup_video, "x", lay_popup_video.x, x),
ObjectAnimator.ofFloat(lay_popup_video, "y", lay_popup_video.y, y),
ValueAnimator.ofInt(layoutParam.width, w).apply {
addUpdateListener {
val animW = it.animatedValue as Int
layoutParam.width = animW
lay_popup_video.requestLayout()
}
},
ValueAnimator.ofInt(layoutParam.height, h).apply {
addUpdateListener {
val animH = it.animatedValue as Int
layoutParam.height = animH
lay_popup_video.requestLayout()
}
},
ObjectAnimator.ofFloat(lay_popup_video_border, "clipScale", lay_popup_video_border.clipScale, b))
start()
}
}
fun closePopupVideo(anchor: PopupVideoAnchor) {
if (popupVideoHolders[anchor]?.isOn?.not() != false) return
popupVideoHolders[anchor]?.isOn = false
if (anchor == PopupVideoAnchor.WEB && (popupVideoHolders[PopupVideoAnchor.QNA]?.isOn == true || popupVideoHolders[PopupVideoAnchor.POLL]?.isOn == true)) {
return
}
val layoutParam = lay_popup_video.layoutParams
for (popupVideoHolder in popupVideoHolders) {
if (popupVideoHolder.value.isOn) {
loadAnimation(R.anim.fade_out).apply {
duration = 150
onEnd {
lay_popup_video.clipScale = 1F
lay_popup_video.x = popupVideoHolder.value.x
lay_popup_video.y = popupVideoHolder.value.y
lay_popup_video.layoutParams.width = popupVideoHolder.value.w
lay_popup_video.layoutParams.height = ((popupVideoHolder.value.w.f / videoSize.w.f) * videoSize.h.f).i
lay_popup_video.requestLayout()
lay_popup_video_border.clipScale = if (popupVideoHolder.value.isBordered) .95F else 1F
lay_popup_video.clearAnimation()
lay_popup_video.startAnimation(loadAnimation(R.anim.fade_in).apply { duration = 150 })
}
lay_popup_video.clearAnimation()
lay_popup_video.startAnimation(this)
}
return
}
}
currPopupVideoAnim?.cancel()
currPopupVideoAnim = AnimatorSet().apply {
duration = 350L
onEnd {
findFragment(GameAVStreamFragment::class.java)?.returnPlayer()
pv_popup.player = null
lay_popup_video.invisible()
}
playTogether(
ObjectAnimator.ofFloat(lay_popup_video, "clipScale", lay_popup_video.clipScale, 3.5F),
ObjectAnimator.ofFloat(lay_popup_video, "x", lay_popup_video.x, 0F),
ObjectAnimator.ofFloat(lay_popup_video, "y", lay_popup_video.y, 0F),
ValueAnimator.ofInt(layoutParam.width, videoSize.w).apply {
addUpdateListener {
val animW = it.animatedValue as Int
layoutParam.width = animW
lay_popup_video.requestLayout()
}
},
ValueAnimator.ofInt(layoutParam.height, videoSize.h).apply {
addUpdateListener {
val animH = it.animatedValue as Int
layoutParam.height = animH
lay_popup_video.requestLayout()
}
},
ObjectAnimator.ofFloat(lay_popup_video_border, "clipScale", lay_popup_video_border.clipScale, 3.5F))
start()
}
}
And the showPopupVideo() above is mainly called in another Fragment, while closePopupVideo() is called when a 10-second timer is finished:
...
val streamFragment = findFragment(GameAVStreamFragment::class.java)
if (streamFragment?.isAudioOnly?.not() == true) {
if (activity.popupVideoHolders[GamePlayActivity.PopupVideoAnchor.WEB]?.isOn == true) {
activity.arrangeView(R.id.lay_popup_video, R.id.lay_qna)
} else {
activity.arrangeView(R.id.lay_head, R.id.lay_popup_video, R.id.lay_qna)
}
activity.showPopupVideo(GamePlayActivity.PopupVideoAnchor.QNA)
} else {
vw_hole_cover.visible()
}
...
...might it be a threading issue?
FindViewById may return null if the id given is not in your associated view. Or, if you're calling findviewbyid on a view group, the viewgroup doesn't hold the view that have the specific id.
If you are using id which is not available in XML layout in that case it will suddenly return null.
For e.g.
Layout(XML):
<TextView
android:id="tv_sample"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
Activity(Java):
Textview tvSample = findViewById(R.id.tv_sam);
Check the id's of declaring in layout and activity files, so that it will fire NullPointerException.
I have a setOnClickListener on two buttons that just runs the code in two functions.
I want to create a safe call so when I click the buttons the app doesn't crash. I thought I would have to make them false or something, but apparently it doesn't work.
How should I do it?
Thanks
if (weightView.text.isEmpty() && percentageView.text.isEmpty()) {
calculation() == false
lbsCalculation() == false
} else {
calculation()
lbsCalculation()
}
These are my two clickListeners
calculateBtn.setOnClickListener{
calculation()
}
lbsCalculationBtn.setOnClickListener{
lbsCalculation()
}
Functions:
fun calculation () {
var weightValue = weightView.text.toString().toInt()
var percentageValue = percentageView.text.toString().toInt()
var result = (weightValue * percentageValue) / 100.toDouble()
var resultFormat = "%.1f KG".format(result)
resultView.text = resultFormat.toString()
}
fun lbsCalculation() {
var weightValue = weightView.text.toString().toInt()
var percentageValue = percentageView.text.toString().toInt()
var result = ((weightValue * percentageValue) / 100) * 2.2.toDouble()
var resultFormat = "%.1f LBS".format(result)
resultView.text = resultFormat.toString()
}
=================
Picture
Do it inside your method. Something like this:
public class calculation(String weightView, String percentageView){
//you make a condition that should do something if strings are empty
if(weightView && percentageView == null)
//DO WHAT YOU WANT
}else{
//Show an alert or something that your EditText is empty.
}
and should call it in like this:
calculateBtn.setOnClickListener{
calculation(weightView.getText.toString(),percentageView.getText.toString())
}
======EDIT=======
Your function should look like this:
fun calculation(weightValue: String,percentageValue: String)
{
if(weightValue == null && percentageValue == null ){
var myWeightValue = weightValue.toInt();
var myPercentageValue = percentageValue.toInt();
var result = (myWeightValue * myPercentageValue) / 100
var resultFormat = "%.1f KG".format(result)
resultView.text = resultFormat
}
else {
**SHOW A MESSAGE LIKE A TOAST OR ALERTDIALOG TO LET THE USER KNOW THAT WEIGHT AND PERCENTAGE IS REQUIRED**
}
}
fun lbsCalculation() {
var weightValue = weightView.text.toString().toInt()
var percentageValue = percentageView.text.toString().toInt()
var result = ((weightValue * percentageValue) / 100) * 2.2
var resultFormat = "%.1f LBS".format(result)
resultView.text = resultFormat
}
Your onClick should look like this:
calculateBtn.setOnClickListener{
calculation(weightView.text,percentageView.text)
}
I have to display realtime data in the app. I chose MPAndroidChart as a library for charts.
But without settings initial dataset with entries or setting axis minimum and axis maximum (x axis) my chart isn't displayed (with this settings I can't scroll chart). I almost already copied all code from the example, but it's still invisible (example starts without bounds of x axis and works good):
https://github.com/PhilJay/MPAndroidChart/blob/master/MPChartExample/src/com/xxmassdeveloper/mpchartexample/RealtimeLineChartActivity.java
Here is example with fixed max/min X
Here is example of bug, no data shown:
Everything is inside the fragment in the viewpager:
Here is code of the initing line chart:
line_chart.apply {
description.apply {
isEnabled = true
text = "Live chart"
}
setTouchEnabled(true)
isDragEnabled = true
setScaleEnabled(true)
setDrawGridBackground(false)
setPinchZoom(true)
data = LineData().apply {
setValueTextColor(Color.BLACK)
}
// THIS:
// data = LineData(dataset("Simple").apply {
// for (i in 1..10) {
// addEntry(Entry(i.toFloat(), i.toFloat()))
// }
// })
legend.apply {
form = LINE
textColor = Color.BLACK
}
xAxis.apply {
textColor = Color.BLACK
setDrawGridLines(false)
setAvoidFirstLastClipping(true)
isEnabled = true
granularity = 5f
axisMinimum = 0f
axisMaximum = 20f
setValueFormatter { value, _ -> Date(value.toLong()).simpleSecondsFormat() }
}
axisLeft.apply {
textColor = Color.BLACK
// OR THIS:
//axisMinimum = 0f
//axisMaximum = 12f
setDrawGridLines(true)
}
axisRight.isEnabled = false
setVisibleXRangeMaximum(100f)
}
Code for creating dataset:
fun dataset(label: String): LineDataSet = LineDataSet(null, label).apply {
axisDependency = LEFT
color = Color.BLACK
setCircleColor(Color.RED)
lineWidth = 2f
circleRadius = 4f
fillAlpha = 65
fillColor = Color.RED
highLightColor = Color.GREEN
valueTextColor = Color.RED
valueTextSize = 9f
setDrawValues(true)
}
Data comes from the store, using RxJava2:
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { Timber.d("Entry: $it, timestamp: ${it.timestamp}") }
.doOnNext {
line_chart.data.apply {
val dataSet = getDataSetByLabel(it.macAddress, true) ?: dataset(
it.macAddress).also { addDataSet(it) }
addEntry(Entry(it.timestamp.toFloat(), it.energy),
getIndexOfDataSet(dataSet))
notifyDataChanged()
}
}
.doOnNext {
line_chart.apply {
notifyDataSetChanged()
setVisibleXRangeMaximum(120f)
invalidate()
}
}
Thanks!
Accidentally fixed issue, by removing line setVisibleXRangeMaximum(100f) from the first snippet.