添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

in my current project there's an EditText with fixed layout_width and layout_height, called exercise that is expanded downwards programmatically: One line of text (String) + "\n" is added to the EditText.

Sometimes the line added to the EditText, let's call it element, is too long to fit inside the full width of the object so it's splitted into a new line. The thing is I would either like the lines' text size in exercise to be resized to fit the EditText's width or have a clear visible distance between each line (element), but just not inside the newline due to not fitting inside the exercise 's width.

Therefore I googled as much as I could and tried out every possible solution I could find today. What I tried:

  • Using either EditText as the object and android:autoSizeTextType="uniform" & android:inputType="textMultiLine|textCapSentences" as attributes or androidx.appcompat.widget.AppCompatEditText , accompanied by the attributes app:autoSizeMaxTextSize="28sp" , app:autoSizeMinTextSize="8sp" and app:autoSizeStepGranularity="1sp" (with a device that supports just exactly API 26)
  • using other types of text objects
  • using lineSpacingExtra to insert some spacing. This unfortunately also inserted the spacing between the wrapped/ splitted line so the original element's line that got splitted by wrapping inside the EditText had the spacing aswell.
  • That's where I am now. I can't get the text size be reduced automatically when the line would be too wide for the EditText's width.

    I could supply the full XML, if needed.

    I'm grateful for any hint that could help here. Thanks in advance!

    Auto-sizing text affects the size of all the text in the TextView , it should work fine for a single line (also if you use app:autoSizeTextType instead of android: it'll work down to API 14). You could try messing around with Spannable s to change the size of different lines of text in the same TextView , but are you sure you don't just want a list view of some kind? That way each entry is completely independent, has its own TextView that can be sized appropriately, can be easily edited without having to go to the correct line etc cactustictacs Jul 19, 2022 at 15:48 Thanks first of all for your advice! I also tried the app:autoSizeTextType object for compability reasons with no effect. Not sure if Spannable will be helpful enough/ won't get too messy but I will have a look at it. Of course I thought about just going with a List View but I remembered working myself for days through half an hour tutorial videos of difficult to understand people on YouTube which sometimes left me with more errors than I began with. Plus I would like to save the exercise 's contents for later re-use so I found it easier to save and re-import a string than a list. Malte Ge Jul 19, 2022 at 21:43 I've put a basic implementation in an answer for ya, just so you can see how it could work. I know a lot of people watch video tutorials now, but honestly a well-written tutorial article is a lot easier for me to work with - plus you can skim it and get an idea if it's decent or not. And storing a list of String s isn't much harder than a String on its own, and you're keeping separate things separate in your logic. Whatever works for you, but if you're taking data and smooshing it into one big string for convenience, you'll probably hit a point where it's not very convenient anymore! cactustictacs Jul 20, 2022 at 18:54

    Here's a really basic RecyclerView implementation (using view binding, let me know if you're not familiar with that - you can just findViewById all the things instead):

    class MainFragment : Fragment(R.layout.fragment_main) {
        lateinit var binding: FragmentMainBinding
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            binding = FragmentMainBinding.bind(view)
            with(binding) {
                val adapter = MyAdapter()
                recyclerView.adapter = adapter
                recyclerView.layoutManager = LinearLayoutManager(requireContext())
                addButton.setOnClickListener {
                    val item = textEntry.text.toString()
                    if (item.isNotBlank()) {
                        adapter.addItem(item)
                        textEntry.text.clear()
    class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
        private var data: List<String> = emptyList()
        fun addItem(item: String) {
            data = data + item
            notifyItemInserted(data.lastIndex)
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view = ItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return ViewHolder(view)
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            holder.binding.textView.text = data[position]
        override fun getItemCount(): Int = data.size
        class ViewHolder(val binding: ItemViewBinding) : RecyclerView.ViewHolder(binding.root)
    
    fragment_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:orientation="vertical"
        android:gravity="center"
        android:padding="10dp">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/textEntry"
        <EditText
            android:id="@+id/textEntry"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:singleLine="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/addButton"
        <Button
            android:id="@+id/addButton"
            android:text="ADD"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
    </androidx.constraintlayout.widget.ConstraintLayout>
    
    item_view.xml
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingHorizontal="16dp"
        <TextView
            android:id="@+id/textView"
            app:autoSizeTextType="uniform"
            android:layout_width="match_parent"
            android:layout_height="48sp"
            android:maxLines="1"
            android:gravity="center_vertical"
    </FrameLayout>
    

    It's pretty simple - you have a text entry field and a button to add the contents as a new line. The button passes the contents to addItem on the adapter, which appends it to the list of lines in data. The RecyclerView just displays all the items in data, using a ViewHolder layout that has an auto-sizing TextView to scale each item.

    Ideally you'd want to persist data somehow (e.g. the Add button passes the new data to a ViewModel, stores it somehow, updates the current list which the adapter has observed so it updates whenever there's a change) - I just left it as a basic proof of concept. Also, it's easier to store separate items if they're kept separate - you can always serialise it by joining them into a single string if you really want! But generally you wouldn't want to do that

    edit - since you're having trouble with the setTypeface thing, this is all it is:

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        with(holder.binding.textView) {
            val styled = position % 2 == 0
            text = data[position]
            setTypeface(typeface, if (styled) Typeface.BOLD else Typeface.NORMAL)
            setTextColor(if (styled) Color.RED else Color.BLACK)
    

    The logic is just styling alternate items differently, but hopefully you get the idea. You decide how to style a given item depending on what it is, and then you apply that style by setting attributes as appropriate. It's always an "if this is true do A, otherwise do B" situation, so you're always setting the attribute one way or the other. You never only set it for one case, because then you're leaving old state displayed if it's not that case.

    It's more complicated, but you also have the option of creating different ViewHolders (with their own XML layouts) for different kinds of item. So instead of having a single ViewHolder that has to work with everything, where you have to reconfigure things like all the styling in onBindViewHolder depending on which type of item is displayed, you can just have different ViewHolders with different styling, different layouts etc:

    // creating a sealed class so we can say our adapter handles a MyViewHolder type,
    // and we can have a specific set of possible subclasses of that
    sealed class MyViewHolder(view: View) : RecyclerView.ViewHolder(view)
    class HeaderViewHolder(val binding: HeaderItemBinding) : MyViewHolder(binding.root)
    class ItemViewHolder(val binding: ItemViewBinding) : MyViewHolder(binding.root)
    // the Adapter now uses the MyViewHolder type (which as above, covers a couple of different
    // ViewHolder classes we're using)
    class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
        private var data: List<String> = emptyList()
        fun addItem(item: String) {
            data = data + item
            notifyItemInserted(data.lastIndex)
        // some identifiers for the different ViewHolder types we're using
        private val HEADER_TYPE = 0
        private val ITEM_TYPE = 1
        override fun getItemViewType(position: Int): Int {
            // Work out what kind of ViewHolder the item in this position should display in.
            // This gets passed to onCreateViewHolder, where you create the appropriate type,
            // and that type of ViewHolder is what gets passed into onBindViewHolder for this position
            return if (data[position].startsWith("Section")) HEADER_TYPE else ITEM_TYPE
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
            val inflater = LayoutInflater.from(parent.context)
            // creating the appropriate ViewHolder instance depending on the type requested
            return when(viewType) {
                HEADER_TYPE -> HeaderViewHolder(HeaderItemBinding.inflate(inflater, parent, false))
                ITEM_TYPE -> ItemViewHolder(ItemViewBinding.inflate(inflater, parent, false))
                else -> throw RuntimeException("Unhanded view type!")
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            // The type of MyViewHolder passed in here depends on what getItemViewType returns
            // for this position - binding is a different type in each case,
            // because it's been generated from different layouts
            when(holder) {
                is HeaderViewHolder -> holder.binding.textView.text = data[position]
                is ItemViewHolder -> holder.binding.textView.text = data[position]
        override fun getItemCount(): Int = data.size
    

    (You could be a bit more clever than this, but just to illustrate the general idea!)

    That's using the same item_view.xml as before, and a header_item.xml variation on that (but you could have literally anything, they're completely separate layouts, completely separate ViewHolders):

    header_item.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingHorizontal="16dp"
        <TextView
            android:id="@+id/textView"
            app:autoSizeTextType="uniform"
            android:layout_width="match_parent"
            android:layout_height="48sp"
            android:layout_weight="1"
            android:textStyle="bold"
            android:textColor="#DD1100"
            android:maxLines="1"
            android:gravity="center_vertical"
    </LinearLayout>
    

    So instead of having to "redesign" one ViewHolder in code to go back and forth between different item types and their styling, You can just use two differently-styled layouts. It's a bit more work to set up, but it can be neater and much more flexible when you have completely independent things - especially if you want to give them different functionality. It depends whether it's worth it to you, or if you're happy to just have a single ViewHolder and restyle things in code, hide or show elements etc.

    Thank you very much for your efforts! I agree with you, it's far easier to follow a well written article about such an implementation than watch somebody on youtube write some code and explain in broken english. I have to solve an error with button colors first, then I will give it another shot with the recycler view, adapters and stuff from your answer. I will keep you updated on the implementation process. Thanks for the motivation to be more professional here :) – Malte Ge Jul 21, 2022 at 10:23 @MalteGe no worries, glad you got it working! It's a little more complicated than just having one big TextView but I think it'll be easier to manage, and probably nicer for the user too. btw if your button's showing up white that might be a theming issue, that up there is just the base Material theme. If your buttons are all white (and their text is still the default white too) it'll look like nothing's there on a white background – cactustictacs Jul 26, 2022 at 17:38 @MalteGe in onBindViewHolder you have to set up the entire ViewHolder to reflect the current item's state. So if the current item should be bold, you need to set Typeface.BOLD on it - otherwise you need to set it to Typeface.NORMAL because there's a possibility it's already been set to bold for the last item it displayed. You're reusing these ViewHolders so you have to set up their whole state each time for all the bits that could change. If you mean you want to style part of the text in a single TextView, not the whole thing, you need to use a Spannable like in that link – cactustictacs Jul 29, 2022 at 23:22 @MalteGe nah don't worry about it, but thanks! You just set the typeface in onBindViewHolder, same as how you set the text it's displaying in there. That function gets called by the adapter when it needs to display the item at position in the list, so you're just configuring the reusable ViewHolder instance it's giving you. It's the same as setting any other attribute - setting a background colour, deciding if a box should be checked, displaying a particular image etc. However the data should be represented, that's where you do it. I'll throw an example in my answer – cactustictacs Jul 30, 2022 at 1:43 Thank you so much man! Really appreciating your efforts to explain this new solution, makes sense to me now. The solution with modulo is a neat one for a quick typeface switch. I wish I could use this for the header usecase but suppose I'll be needing a second viewholder. And here you provided the exact thing I was missing - how to differentiate between two viewholders - I didn't know about sealed classes but they indeed do the trick. I will implement this when I'm having a bit more time later the week, trying to work with the one viewholder and your first solution until then. Thanks again :) – Malte Ge Aug 2, 2022 at 12:31 Thanks for the advice! Do you know if it is possible to have this affect just a single line rather than the whole EditText? – Malte Ge Jul 19, 2022 at 21:44 I already tested the impact of android:lines="1"on my app (sorry I didn't say earlier) and when there's too much text for one line so it would be escaped in a second line, this line simply gets cut out. So this is no solution either :( – Malte Ge Jul 20, 2022 at 11:31

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.