Using Jetpack Compose with Presentation Class - android

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.

Related

Add View Not Covering Full Screen

Attached Layout and Code Please help me to get my Remote video in full screen
Adding View to Layout Code
private fun addVideoViews() {
if (mVideoViewsAdded || sinchServiceInterface == null) {
return //early
}
val vc = sinchServiceInterface!!.getVideoController()
if (vc != null) {
val localView = findViewById<View>(com.ayush.flow.R.id.localVideo) as RelativeLayout
val remoteview = findViewById<View>(com.ayush.flow.R.id.remoteVideo) as RelativeLayout
val lview = vc.localView
val rview=vc.remoteView
localView.addView(lview)
remoteview.addView(rview)
mVideoViewsAdded = true
localView.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
//this toggles the front camera to rear camera and vice versa
vc.toggleCaptureDevicePosition()
}
})
}
}
Thanks in Advance
Enable fullscreen mode.
See this link
https://developer.android.com/training/system-ui/immersive

Android - LinearView - Dynamic instanciation of views

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)
}

Custom Lint Rule to Check All Views in Layouts

In the app I'm working on, it is imperative that all views in all layout files have an id set on them. So I'm trying to build a custom lint rule to enforce this.
Normally, one would use the getApplicableElements() method from XmlScanner and include a list of strings for each element tag. However, I can't seem to find a way to make this look at all elements in an XML layout which subclass View.
I tried using XmlScannerConstants.ALL, however, that looks at every single element in every single XML file. Given that we have several other types of XML-based resources, that's not going to work.
My code for the inspector class is below. Does anyone know of a good way filter getApplicableElements() so it looks at every element that subclasses View, and nothing else?
class IdDetector : ResourceXmlDetector() {
companion object {
private const val ISSUE_ID = "MissingId"
private const val ISSUE_DESCRIPTION = "Missing required attribute 'id'"
private const val ISSUE_EXPLANATION = "Identifiers are required on all views."
val ISSUE = Issue.create(
id = ISSUE_ID,
briefDescription = ISSUE_DESCRIPTION,
explanation = ISSUE_EXPLANATION,
category = Category.A11Y,
priority = 10,
severity = Severity.FATAL,
androidSpecific = true,
implementation = Implementation(IdDetector::class.java, Scope.RESOURCE_FILE_SCOPE)
)
}
override fun getApplicableElements(): Collection<String>? = XmlScannerConstants.ALL
override fun visitElement(context: XmlContext, element: Element) {
if (!element.hasAttributeNS("http://schemas.android.com/apk/res/android", "id")) {
context.report(ISSUE, element, context.getLocation(element), ISSUE_DESCRIPTION)
}
}
}
There's the appliesTo(#NonNull ResourceFolderType folderType) method that can help you achieve that. In your case, I believe you will be only interested in targeting the layout folder. You're new detector would look something like this:
class IdDetector : ResourceXmlDetector() {
companion object {
private const val ISSUE_ID = "MissingId"
private const val ISSUE_DESCRIPTION = "Missing required attribute 'id'"
private const val ISSUE_EXPLANATION = "Identifiers are required on all views."
val ISSUE = Issue.create(
id = ISSUE_ID,
briefDescription = ISSUE_DESCRIPTION,
explanation = ISSUE_EXPLANATION,
category = Category.A11Y,
priority = 10,
severity = Severity.FATAL,
androidSpecific = true,
implementation = Implementation(IdDetector::class.java, Scope.RESOURCE_FILE_SCOPE)
)
}
override fun appliesTo(folderType: ResourceFolderType): Boolean = ResourceFolderType.LAYOUT == folderType
override fun getApplicableElements(): Collection<String>? = XmlScannerConstants.ALL
override fun visitElement(context: XmlContext, element: Element) {
if (!element.hasAttributeNS("http://schemas.android.com/apk/res/android", "id")) {
context.report(ISSUE, element, context.getLocation(element), ISSUE_DESCRIPTION)
}
}
}

constraintlayout.widget.Group animation not working with TransitionManager

does anyone has any idea why animating constraintlayout.widget.Group visibility with TransitionManager is not working? Isn't this widget made for these kind of things?
It is working if hiding or showing items after separating views from Group
<androidx.constraintlayout.widget.Group
android:id="#+id/cardHeadersGroup"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="invisible"
app:constraint_referenced_ids="cardSystemHeader,cardSimpleHeader,cardCombinedHeader"
app:layout_constraintBottom_toBottomOf="#+id/cardCombinedHeader"
app:layout_constraintEnd_toEndOf="#+id/cardSystemHeader"
app:layout_constraintStart_toStartOf="#+id/cardSimpleHeader"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible"/>
val headersGroup = binding.cardHeadersGroup
val slideIn = Slide()
slideIn.slideEdge = Gravity.BOTTOM
slideIn.mode = Slide.MODE_IN
slideIn.addTarget(headersGroup)
TransitionManager.beginDelayedTransition(binding.root as ViewGroup, slideIn)
headersGroup.visibility = VISIBLE
I've been recently working with TransitionManager and ConstraintLayout.Group and found it to be very buggy.
Eventually I decided to dump the whole ConstraintLayout.Group and created an in-code AnimationGroup (similar to the in-xml ConstraintLayout.Group):
class AnimationGroup(vararg val views: View) {
var visibility: Int = View.INVISIBLE
set(value) {
views.forEach { it.visibility = value }
field = value
}
}
and an extension function for the Transition:
private fun Transition.addTarget(animationGroup: AnimationGroup) {
animationGroup.views.forEach { viewInGroup -> this.addTarget(viewInGroup) }
}
That way you can do the following (almost exactly the same code, but simpler xml - no ConstraintLayout.Group):
val headersGroup = AnimationGroup(
binding.cardSystemHeader,
binding.cardSimpleHeader,
binding.cardCombinedHeader
)
val slideIn = Slide()
slideIn.slideEdge = Gravity.BOTTOM
slideIn.mode = Slide.MODE_IN
slideIn.addTarget(headersGroup)
TransitionManager.beginDelayedTransition(binding.root as ViewGroup, slideIn)
headersGroup.visibility = VISIBLE
We can also extract the Group's referenced views with simple extension function:
fun Group.getReferencedViews() = referencedIds.map { rootView.findViewById<View>(it) }

How to circular reveal the entire activity

I tried using android:windowEnterTransition and android:windowExitTransition but that seems to animate each view in the activity, i.e. revealing each view separately. How can I animate the whole activity with content on it? There are no shared elements between two activities.
There are a couple of ways to animate the entire Activity. The most efficient mechanism is using Window Transitions. These operate against the Window so the content does not need to be redrawn on each frame. The down side is that the operations are limited to the older Animation Framework.
Typically, you'd specify the window animations using a style. You can see how it is done here: Start Activity with an animation
You can also use overridePendingTransition or ActivityOptions.makeCustomAnimation
If you want to use the lollipop Activity Transitions framework, you can use windowEnterTransition. If you want just your content to be operated on, set the outermost ViewGroup to have:
<WhateverViewGroup ... android:transitionGroup="true"/>
You may want to give your view group a name or id and use it in the enter transition so that it targets only that group. Otherwise it will target things like the status bar background also.
If you want it to operate on the entire Window contents:
getWindow().getDecorView().setTransitionGroup(true)
This will force the window contents to act as a unit.
After a lot of research and android source code reading, I figured out how to do this. It's in Scala but you should translate that to Java easily.
The following are the most important parts.
CircularRevealActivity.scala:
override protected def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
val window = getWindow
val decor = window.getDecorView.asInstanceOf[ViewGroup]
// prevent fading of background
decor.setBackgroundColor(android.R.color.transparent)
if (Build.version >= 21) {
window.setEnterTransition(circularRevealTransition)
window.setReturnTransition(circularRevealTransition)
// decor.setTransitionGroup(true) won't work
for (i <- 0 until decor.getChildCount) {
val child = decor.getChildAt(i).asInstanceOf[ViewGroup]
if (child != null) child.setTransitionGroup(true)
}
if (savedInstanceState == null) {
val intent = getIntent
val x = intent.getFloatExtra(EXTRA_SPAWN_LOCATION_X, Float.NaN)
if (!x.isNaN) {
val y = intent.getFloatExtra(EXTRA_SPAWN_LOCATION_Y, Float.NaN)
if (!y.isNaN) circularRevealTransition.spawnLocation = (x, y)
}
}
}
}
CircularReveal.scala:
#TargetApi(21)
class CircularReveal(context: Context, attrs: AttributeSet = null)
extends Visibility(context, attrs) {
var spawnLocation: (Float, Float) = _
var stopper: View = _
private val metrics = new DisplayMetrics
private lazy val wm = context.getSystemService(Context.WINDOW_SERVICE)
.asInstanceOf[WindowManager]
private def getEnclosingCircleRadius(x: Float, y: Float) =
math.hypot(math.max(x, metrics.widthPixels - x),
math.max(y, metrics.widthPixels - y)).toFloat
override def onAppear(sceneRoot: ViewGroup, view: View,
startValues: TransitionValues, endValues: TransitionValues) = {
wm.getDefaultDisplay.getMetrics(metrics)
val (x, y) = LocationObserver.getRelatedTo(spawnLocation, view)
new NoPauseAnimator(ViewAnimationUtils
.createCircularReveal(view, x.toInt, y.toInt, 0,
getEnclosingCircleRadius(x, y)))
}
override def onDisappear(sceneRoot: ViewGroup, view: View,
startValues: TransitionValues, endValues: TransitionValues) = {
wm.getDefaultDisplay.getMetrics(metrics)
val (x, y) = if (stopper == null)
LocationObserver.getRelatedTo((metrics.widthPixels * .5F,
metrics.heightPixels.toFloat), view)
else LocationObserver.getRelatedTo(stopper, view)
new NoPauseAnimator(ViewAnimationUtils
.createCircularReveal(view, x.toInt, y.toInt,
getEnclosingCircleRadius(x, y), 0))
}
}

Categories

Resources