I am trying to intercept every view widget that is being inflated in my activity and override the setText functionality of that view if it exists.
So if a TextView has a android:text="bla" in the XML layout i want to be able to intercept that and maybe add a ** at the end of all texts being set from the xml.
One way that seems to be close to what i need is to use a Custom Layout inflator.
LayoutInflaterCompat.setFactory(layoutInflator, InflatorOnSteriods(this))
and the in my InflatorOnSteriods to override onCreateView and then intercept all views there.. this approach doesn't seem to work at all. onCreateView is never called.
I tried also to use cloneInContext
LayoutInflaterCompat.setFactory(layoutInflater.cloneInContext(this), InflatorOnSteriods(this))
But no luck as well, maybe my approach is totally wrong i am also open to a different way where i can intercept all views being presented and to be specific set a certain attribute on that view. It is really important to make sure that i will be the last one changing that view and make sure the system respects my changes and wont override them later.
Update:
Although i don't think its relevant; Code of InflatorOnSteroids.kt
class InflatorOnSteriods(val appCompatActivity: AppCompatActivity) : LayoutInflaterFactory {
override fun onCreateView(parent: View, name: String, context: Context, attrs: AttributeSet): View {
var result: View
if (TextUtils.equals(name, "DebugDrawerLayout")) {
result = ImageView(context, attrs)
}
result = appCompatActivity.onCreateView(name, context, attrs)
if (result == null) {
// Get themed views from app compat
result = appCompatActivity.delegate.createView(parent, name, context, attrs)
}
return result
}
}
After some time troubleshooting my solution i finally managed to achieve what i wanted with the inflator factory solution.
First i create an abstract activity that has a custom inflator set to it.
abstract class SteroidsActivity : AppCompatActivity() {
var mInflater: LayoutInflater? = null
abstract fun getActivityLayout(): Int
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mInflater = layoutInflater?.cloneInContext(this)
LayoutInflaterCompat.setFactory(mInflater, InflaterOnSteroids(this))
setContentView(getActivityLayout())
}
override fun getLayoutInflater(): LayoutInflater? {
return mInflater
}
override fun getSystemService(name: String): Any? {
if (name == LAYOUT_INFLATER_SERVICE) {
if (mInflater == null) {
mInflater = super.getSystemService(name) as LayoutInflater
}
return mInflater
}
return super.getSystemService(name)
}
}
Second thing that you need to do is create your custom inflator factory
class InflaterOnSteroids(appCompatActivity1: AppCompatActivity) : LayoutInflaterFactory {
override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {
//Do stuff here and return a view
return null
}
}
The problem with my code was that it was always crashing with a weird error that i couldn't troubleshoot until i realised that i need to add a ? after View since i'm using kotlin and parent view can be null :)
Happy programming
Good reference can be found here
Related
I am using this library to put a carousel view in an Android app: https://github.com/ImaginativeShohag/Why-Not-Image-Carousel
I'm also trying to use the showcase type, but a prerequisite to use this type is creating a custom layout for the carousel items.
Creating the layout I understand, but the OP uses this example in Kotlin to show how the custom layout is actually used:
binding.carousel3.carouselListener = object : CarouselListener {
override fun onCreateViewHolder(
layoutInflater: LayoutInflater,
parent: ViewGroup
): ViewBinding? {
return ItemCustomFixedSizeLayout1Binding.inflate(layoutInflater, parent, false)
}
override fun onBindViewHolder(
binding: ViewBinding,
item: CarouselItem,
position: Int
) {
val currentBinding = binding as ItemCustomFixedSizeLayout1Binding
currentBinding.imageView.apply {
scaleType = imageScaleType
// carousel_default_placeholder is the default placeholder comes with
// the library.
setImage(item, R.drawable.carousel_default_placeholder)
}
}
}
val listThree = mutableListOf<CarouselItem>()
for (item in DataSet.three) {
listThree.add(
CarouselItem(
imageUrl = item.first,
caption = item.second
)
)
}
binding.carousel3.setData(listThree)
binding.customCaption.isSelected = true
binding.carousel3.onScrollListener = object : CarouselOnScrollListener {
override fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int,
position: Int,
carouselItem: CarouselItem?
) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
carouselItem?.apply {
binding.customCaption.text = caption
}
}
}
override fun onScrolled(
recyclerView: RecyclerView,
dx: Int,
dy: Int,
position: Int,
carouselItem: CarouselItem?
) {
// ...
}
}
// Custom navigation
binding.btnGotoPrevious.setOnClickListener {
binding.carousel3.previous()
}
binding.btnGotoNext.setOnClickListener {
binding.carousel3.next()
}
I'm having some trouble figuring out what exactly this code is doing and how it would look in Java. Any pointers would be greatly appreciated!
A Quick Guess
It seems that the listener is providing the callback of Recycler View. If you need me to guess within a second, and I will say the custom view is a Recycler View using listener to allow users to register the Recycler View methods (which is the Adapter using in the RV)
Deep Investigation
1st question: What is the Custom View class for id=carousel3 in KotlinActivity in the sample project
Ans: org.imaginativeworld.whynotimagecarousel.ImageCarousel.
(P.S. identical between activity_kotlin.xml and activity_test.xml)
(Below is a screen cap, don't try to click the links since it will not work :))
Let's got to search ImageCarousel and we will find ImageCarousel.kt. Let's find CarouselListener in there
We can see that when CarouselListener is set, it will immediately assign to adapter?.listener (Just ignore the "?" sign if you are not familiar with Kotlin)
2nd question: What is adapter here?
Ans from the same file:
private var adapter: FiniteCarouselAdapter? = null
3rd question: What is FiniteCarouselAdapter?
Ans: Its a RecyclerView.Adapter
open class FiniteCarouselAdapter(
...
) : RecyclerView.Adapter<FiniteCarouselAdapter.MyViewHolder>() {
Last question: How is it related to FiniteCarouselAdapter#listener/CarouselListener/adapter?.listener?
When the RecyclerView#Adapter requires to call the ViewHolder method, it will call to CarouselListener methods instead.
In FiniteCarouselAdapter:
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
...
// Init listeners
listener?.onBindViewHolder(
holder.binding,
item,
realItemPosition
)
As CarouselListener is an interface, the method implementation will be defined in the KotlinActivity instead.
In KotlinActivity:
The above code in your question : )
I'm trying to make something like below:
On that screenshot of facebook lite app:
At part marked "1": is a vertical recyclerview which contains posts.
At part marked "2": is a horizontal recyclerview which contains the stories.
At part marked "3": is the same recyclerview as at part marked "1" which contains posts.
I have already made the recyclerview for posts and It works well. Now I want to know how should I make the recyclerview for stories or friendship suggestion and make the two recyclerviews appear like on Facebook app ?
How could I have recyclerviews similar to the one in facebook app ?
Facebook show multiples recyclerViews One vertical where it shows the posts some others horizontal where it
shows stories or sometimes friendship suggestion.
Do you undestand me ?
Please tell me if I should explain more my issue.
Thanks.
you can define different View Holders for a Recycler List
the horizontal lists are just a view holder with another recycler inside them but with horizontal orientation in the main list. you can choose which view holder you want to use in OnCreatViewHolder
You should implement RecyclerView with multi-view type which gives you the various layout on a single RecyclerView adapter.
By doing this you need a model class which should have a field for define the different type.
Please have a look below sample code
Sample Model class
data class Sample(
var a: String = ""
...
var type: String = ""
)
Field type in model class above will define a unique view-type of adapter
Sample Adapter Class
class SampleAdapter(
private val context: Context,
private val items: ArrayList<Sample>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val HEADER = 1
private const val STORY = 2
private const val FEED = 3
}
override fun getItemViewType(position: Int): Int {
return when(items[position].type) {
Constant.HEADER -> HEADER
Constant.STORY -> STORY
else -> FEED
}
}
override fun getItemCount(): Int = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
HEADER -> HeaderViewHolder(LayoutInflater.from(context).inflate(R.layout_header, parent, false))
STORY -> StoryViewHolder(LayoutInflater.from(context).inflate(R.layout_story, parent, false))
else -> FeedViewHolder(LayoutInflater.from(context).inflate(R.layout_feed, parent, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder) {
is HeaderViewHolder -> holder.onBind()
is StoryViewHolder -> holder.onBind()
is FeedViewHolder -> holder.onBind()
}
}
inner class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun onBind() {
....your business logic
}
}
inner class StoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun onBind() {
....your business logic
}
}
inner class FeedViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun onBind() {
....your business logic
}
}
}
And when you are using the above adapter in Activity or Fragment. You should define the type of each item in ArrayList
For example: samples.add(Sample(...., type=Constant.Header))
My Activity:
class PlayerDetails : AppCompatActivity(), View.OnClickListener {
private lateinit var binding: ActivityPlayerDetailsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_player_details)
}
I'm trying to understand how the data binding process works, this is how I understand it:
private lateinit var binding: ActivityPlayerDetailsBinding
instantiates the ViewDataBinding object.
binding = DataBindingUtil.setContentView(this,
R.layout.activity_player_details) is in 2 parts:
DataBindingUtil.setContentView(this,
R.layout.activity_player_details) sets the content view to the given layout
It then returns the binding object to the binding variable (binding = ...) which can then be used to access views in the layout.
Is this an accurate way of describing how the code is working? I found the source code for DataBindingUtil.java hard to understand. Mostly because setContentView() is being called even though it appears to be assigned instead binding = ....
DataBindingUtil.setContentView(this, R.layout.activity_player_datails) do almost same thing to return binding object. Although DataBindingUtils.setContentView call activity.setContentView before returning.
Instead, I usually override setContentView to make sure assign binding object into variables and sets content to the given layout.
override fun setContentView(layoutResID: Int) {
binding = DataBindingUtil.inflate(LayoutInflater.from(context), layoutResID, null, false)
super.setContentView(mBinding.root)
}
One of my coworkers created an inherit from a RecyclerView and added the logic to create its adapter, defined custom list item attributes, and layout manager inside it.
This is an example of his idea:
class CustomRecyclerView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
init {
loadCustomAttributes()
setDefaultAdapter()
setDefaultLayoutManager()
}
private fun loadCustomAttributes() {
// Load custom attributes: item background, for example.
}
private fun setDefaultAdapter() {
// Define specific Custom Adapter.
}
private fun setDefaultLayoutManager() {
// Define specific Layout Manager.
}
fun setData(data: List) {
// Set data and notify dataset changed.
}
private class CustomAdapter : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
// Specific Adapter.
}
private class ViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
// Specific View Holder.
}
}
I understand his reasons, and it's very reasonable: he wants to plug a View on the XML and just reuse it, without defining the adapter or anything else on the Activity or Fragment; he will just set the data with the method "setData".
But on the other way, I feel that this class is inheriting a RecyclerView just to breaking the design of the RecyclerView and giving too many responsibilities to it. My pain points are:
A RecyclerView shouldn't decide an item attribute (item background colour, for example);
A RecyclerView shouldn’t know about his LayoutManager;
A RecyclerView shouldn't have methods to update the data on the adapter;
A RecyclerView shouldn’t create an instance of his Adapter.
This is an "inheritance over composition" approach.
I tried to find information about it on the internet, but I didn't find anything about good practices when inheriting from a ListView/RecyclerView.
Another idea would be to wrap it inside another layout (ViewGroup or FrameLayout) to encapsulate it. But this would create an unnecessary nested layout, and it will be complicated to test using Espresso (because now the list is private and shouldn't be exposed).
My question is: how to create a reusable RecyclerView using "Composition over Inheritance"?
I have a Java Android application which I want to change to Scala. I have many fragments and I want to know what is the best way to do this in Scala.
This is my Java fragment class MyFragment:
public class MyFragment extends Fragment {
private WebView myWebView;
private TextView myTextView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View myView = inflater.inflate(R.layout.my_view, container, false);
myWebView = (WebView) myView.findViewById(R.id.my_webview);
myWebView.loadUrl("http://www.google.com/");
myTextView = (TextView) myView.findViewById(R.id.my_textview);
myTextView.setText("Google.com");
return myView;
}
}
I always have this base structure: some private UI elements which I instantiate in onCreateView, do some things and return the view (Not shown here: in other on* methods I also do some actions with the UI elements).
I found some articles which do a lazy val like described here: http://blog.andresteingress.com/2011/09/20/programming-android-with-scala/
But in my case, this does not work, because I have fragments and not activities. First I have to inflate the main View myView and then I can get the UI elements of it.
What is the best way to do this in Scala?
--- UPDATE ---
My Scala code looks like this at the moment:
class MyFragment extends Fragment {
private var myTextView: TextView = null
override def onCreateView(inflater: LayoutInflater,
container: ViewGroup, savedInstanceState: Bundle): View = {
val myView = inflater.inflate(R.layout.my_view, container, false)
val myWebView = myView.findViewById(R.id.my_webview).asInstanceOf[WebView]
myWebView.loadUrl("http://www.google.com/")
myTextView = myView.findViewById(R.id.my_textview).asInstanceOf[TextView]
myTextView.setText("Google.com")
myView
}
}
So, what can I improve here? myTextView is a private var because I have to access it several methods in this Fragment. It seems I can not do the stuff explained here: http://blog.andresteingress.com/2011/09/20/programming-android-with-scala/ with lazy val TypedActivity and the implicit conversion of OnClickListener, because I use fragments. So how can I get rid of boilerplate code with .asInstanceOf[T] and make it more Scala like?
Based on your updated code I can only make some suggestion to be more "scala-ish"
Use Option instead of null for your members
private var myWebView: Option[WebView] = None
private var myTextView: Option[TextView] = None
To avoid explicit casting of your views in the code, you need to move it elsewhere, but you cant' get rid of it, because the original android API doesn't give you any clue as to the runtime or compiletime type of the returned objects. To overcome this issue, the post you mentioned uses custom-made typed resources and a trait that handles the types from these.
case class TypedResource[T](id: Int)
object TR {
object id {
val my_webview = TypedResource[TextView](R.id.my_webview)
val my_textview = TypedResource[WebView](R.id.my_textview)
//... you must put here all your typed views referenced by id
}
}
trait TypedViewHolder {
def view: View
//the method explicitly casts to the needed resource type based on the argument
def findView[T](tr: TypedResource[T]): T = view.findViewById(tr.id).asInstanceOf[T]
}
object TypedResource {
//this will implicitly convert your views to a corresponding TypedViewHolder
//this lets you avoid explicit type cast to get your view
implicit def view2typed(v: View): TypedViewHolder = new TypedViewHolder { def view = v }
}
Now we can use the above code
val myView = inflater.inflate(R.layout.my_view, container, false)
val myWebView = myView.findView(TR.id.my_webview)
myWebView.loadUrl("http://www.google.com/")
val myTextView = myView.findView(TR.id.my_textview)
myTextView.setText("Google.com")
Putting both things together
class MyFragment extends Fragment {
private var myWebView: Option[WebView] = None
private var myTextView: Option[TextView] = None
override def onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedInstanceState: Bundle): View = {
//imports the implicit conversion
import TypedResource._
val myView = inflater.inflate(R.layout.my_view, container, false)
myWebView = Some(myView.findView(TR.id.my_webview))
//now we're using options, so we must call methods on the inner value
//we can use Option.map(...) to do it [see http://www.scala-lang.org/api/current/index.html#scala.Option]
myWebView.map(_.loadUrl("http://www.google.com/"))
myTextView = Some(myView.findView(TR.id.my_textview))
//same as above
myTextView.map(_.setText("Google.com"))
myView
}
}
I hope this helps you out. I'm no expert with android so I can only get so far.
Why don't you simply write the fragment in Scala without bothering about "the best way to do this in Scala"? There might be none.
I'd start with removing public from the class definition and including the other goodies - TypedActivity from the article - in the activity. Then, set up the development environment - the IDE - and run the application. If it works, you're done (with the very first step in the migration). I don't think you need lazy val's from the very beginning.
Do small steps so the migration's easier.