简体   繁体   中英

Android Animated Carousel TextView within RecyclerView width wider than screen text gets clipped off. Even when animated into view

I am building a stock ticker type carousel that has market prices running along the screen as shown. The 'CRC' Ticker has only the partial view showing, it clips off at the edge of the parent which is the width of the device even when it is animated into view. I want it to have a width large enough hold all the children that goes past the device width. 股票代码未显示完整的 CBC 指数 Textview

Here if the Carousel layout xml:

<?xml version="1.0" encoding="utf-8"?>
<com.android.forexwatch.views.timepanel.CarouselView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/carouselLinear"
    android:layout_width="match_parent"
    android:layout_height="30dp"
    android:orientation="horizontal"
    android:singleLine="true"
    android:animateLayoutChanges="true"
    />

Here is the class:

package com.android.forexwatch.views.timepanel

import android.content.Context
import android.util.AttributeSet
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.widget.RelativeLayout

import androidx.appcompat.widget.LinearLayoutCompat
import com.android.forexwatch.model.Index
import com.android.forexwatch.R
import com.android.forexwatch.model.CityInfoDetailed
import com.github.ybq.android.spinkit.SpinKitView

class CarouselView(context: Context, attrs: AttributeSet): LinearLayoutCompat(context, attrs) {

    companion object {
        private const val IndexWidth = 600F
    }

    private var contextThemeWrapper: ContextThemeWrapper? = null

    init {
        contextThemeWrapper = ContextThemeWrapper(context.applicationContext, R.style.Theme_ForexWatch)
    }

    fun setupView ( cityInfo: CityInfoDetailed?, isMirror: Boolean = false) {
        createPriceTickerViews(cityInfo, isMirror)
    }

    private fun createPriceTickerViews(cityInfo: CityInfoDetailed?, isMirror: Boolean = false) {
        destroy()
        removeAllViews()
        var calculatedWidth = 0
        cityInfo?.indexes?.forEachIndexed {
            index, element ->
            if (isMirror) index.plus(cityInfo?.indexes.count())
            val loader: SpinKitView = LayoutInflater.from(contextThemeWrapper).inflate(R.layout.preloader, null) as SpinKitView
            val priceTicker: PriceTicker = LayoutInflater.from(contextThemeWrapper).inflate(R.layout.price_ticker, null) as PriceTicker
            addView(priceTicker)
            addView(loader)
            // priceTicker.x = IndexWidth * index
            calculatedWidth.plus(IndexWidth)
            priceTicker.initialize(element, cityInfo, loader)
        }

       layoutParams.width = calculatedWidth
    }

    private fun destroy() {
        for (idx in 0 .. this.childCount) {
            val view = getChildAt(idx)
            if (view is PriceTicker) {
                view.destroy()
            }
        }
    }
}

Here is the parent class:

package com.android.forexwatch.views.timepanel

import android.animation.ValueAnimator
import android.graphics.Color
import android.view.View
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.android.forexwatch.R
import com.android.forexwatch.adapters.TimePanelRecyclerViewAdapter
import com.android.forexwatch.events.TimeEvent
import com.android.forexwatch.model.CityInfoDetailed
import com.android.forexwatch.model.TimeObject
import com.android.forexwatch.utils.TimeKeeper
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe


class TimePanelView(view: View, container: ViewGroup, timePanelAdapter: TimePanelRecyclerViewAdapter) : RecyclerView.ViewHolder(view) {

    companion object {
        private const val OffsetTime: Long = 15 * 60
        private const val FrameRate: Long = 32
    }

    private var fxTime: TextView? = null
    private var stockTime: TextView? = null
    private var city: TextView? = null
    private var currentTime: TextView? = null
    private var fxTimeLabel: TextView? = null
    private var stockTimeLabel: TextView?  = null
    private var carouselView: CarouselView? = null
    private var carouselViewMirror: CarouselView? = null
    private var parentContainer: ViewGroup? = null

    private var adapter: TimePanelRecyclerViewAdapter? = null
    private var cityInfo: CityInfoDetailed? = null
    private var view: View? = null

    init {
        adapter = timePanelAdapter
        this.view = view
        this.parentContainer = container
        EventBus.getDefault().register(this)
    }

    fun setupView(cityInfo: CityInfoDetailed?) {
        this.cityInfo = cityInfo

        city = view?.findViewById(R.id.city)
        stockTime = view?.findViewById(R.id.stockTime)
        fxTime = view?.findViewById(R.id.fxTime)
        fxTimeLabel = view?.findViewById(R.id.fxTimeLabel)
        stockTimeLabel = view?.findViewById(R.id.stockTimeLabel)
        currentTime = view?.findViewById(R.id.currentTime)
        carouselView = view?.findViewById(R.id.carouselLinear)
        carouselViewMirror = view?.findViewById(R.id.carouselLinearMirror)
        city?.text = cityInfo?.cityName

        createCarousel()
    }

    @Subscribe
    fun update(event: TimeEvent.Interval?) {
        updateCurrentTime()
        updateFxTime()
        updateStockTime()
    }

    private fun createCarousel() {
        carouselView?.setupView(cityInfo)
        carouselViewMirror?.setupView(cityInfo, true)
        carouselView?.bringToFront()
        carouselViewMirror?.bringToFront()
        animateCarousel()
    }

    private fun updateCurrentTime() {
        val timezone: String? = cityInfo?.timeZone
        currentTime?.text =  TimeKeeper.getCurrentTimeWithTimezone(timezone, "EEE' 'HH:mm:ss")
    }

    private fun updateFxTime() {
        updateTime(fxTime, fxTimeLabel, cityInfo?.forexOpenTimes, cityInfo?.forexCloseTimes, cityInfo?.timeZone, "FX", Companion.OffsetTime * 8)
    }

    private fun updateStockTime() {
        updateTime(stockTime, stockTimeLabel, cityInfo?.stockOpenTime, cityInfo?.stockCloseTime, cityInfo?.timeZone, cityInfo?.stockExchangeName, Companion.OffsetTime)
    }

    private fun updateTime(timeView: TextView?, timeLabel: TextView?, open: TimeObject?, close: TimeObject?, timezone: String?, type: String?, timeOffset: Long) {

        if (TimeKeeper.isMarketOpen(open, close, timezone)) {
            timeView?.text = TimeKeeper.getTimeDifference(close, close, timezone, true)

            displayMarketColors(timeView, timeOffset, close, timezone, Color.GREEN, true)

            timeLabel?.text = "To " + type + " Close"
        } else {
            timeView?.text = TimeKeeper.getTimeDifference(open, close, timezone, false)

            displayMarketColors(timeView, timeOffset, open, timezone, Color.RED, false)

            timeLabel?.text = "To " + type + " Open"
        }
    }

    private fun displayMarketColors(timeView: TextView?, timeOffset: Long, time: TimeObject?, timezone: String?, outRangeColor: Int, isMarketOpen: Boolean?) {
        val color = if (TimeKeeper.isTimeWithinRange(timeOffset, time, timezone, isMarketOpen)) Color.parseColor("#FF7F00") else outRangeColor
        timeView?.setTextColor(color)
    }

    private fun animateCarousel() {
        if (cityInfo?.indexes?.count() == 1) {
            return
        }

        // carouselView?.x = carouselView?.x?.minus(3.0F)!!
        /* CoroutineScope(Dispatchers.Main).launch {
            delay(FrameRate)
            animateCarousel()
        }*/
        val animator = ValueAnimator.ofFloat(0.0f, 1.0f)
        animator.repeatCount = ValueAnimator.INFINITE
        animator.interpolator = LinearInterpolator()
        animator.duration = 18000L
        animator.addUpdateListener { animation ->
            val progress = animation.animatedValue as Float
            val width: Int? = carouselView?.width
            val translationX = -width?.times(progress)!!
            carouselView?.translationX = translationX!!
            carouselViewMirror?.translationX = translationX!! + width!!
        }
        animator.start()
    }
}

The PriceTicker layout which extends AppCompatTextView

<com.android.forexwatch.views.timepanel.PriceTicker
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    class="com.android.forexwatch.views.timepanel.PriceTicker"
    android:layout_width="240dp"
    android:layout_height="match_parent"
    android:textAlignment="center"
    android:textSize="13sp"
    android:singleLine="true"
    android:background="@drawable/rectangle_shape"
    app:layout_constraintBottom_toBottomOf="parent"
    />

I found the solution by invalidating and requestingLayout on the priceTicker TextView and the container. Also changed the width of the container and priceTicker to the desired width.

    calculatedWidth = calculatedWidth.plus(IndexWidth).toInt()

                priceTicker.initialize(index, cityInfo, loader)
                priceTicker.width = IndexWidth.toInt()
                resetLayout(priceTicker)
            }

            layoutParams.width = (calculatedWidth)
            resetLayout(thisContainer)
        }

    }

    private fun destroy() {
        for (idx in 0 .. this.childCount) {
            val view = getChildAt(idx)
            if (view is PriceTicker) {
                view.destroy()
            }
        }
    }

    private fun resetLayout(view: View?) {
        view?.invalidate()
        view?.requestLayout()
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM