How to add XML layout to custom view? - android

I have a custom view like this:
class SquareView : View {
private lateinit var mRectanglePaint: Paint
constructor(context: Context?) : super(context) {
init(null)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init(attrs)
}
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
mRectanglePaint = Paint().apply {
isAntiAlias = true
color = Color.RED
}
}
override fun onDraw(canvas: Canvas) {
canvas.apply {
drawRectangle(this)
}
}
private fun drawRectangle(canvas: Canvas) {
val rect = Rect()
rect.left = 100
rect.right = rect.left + 100
rect.top = 100
rect.bottom = rect.top + 100
canvas.drawRect(rect, mRectanglePaint)
}
}
And, this is my blue_square.xml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content">
<View
android:id="#+id/blue_square"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#0000FF" />
</LinearLayout>
I want to add this XML layout to my view and set position to bottom of the red square.
I added this code but this time only the blue square appears.
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.blue_square, this, true)
I want to include the blue square in my custom view and change its position.
How can I do that? Thank you for your help.

When I said 2 different approaches, so the first is in a bit more detail but I'll also outline the others.
1)The first is to inflate and layout the xml to a Bitmap and then draw that Bitmap to you Canvas in the right place.
Inflate your xml BUT don't attach it or parent it to your custom view (as that really replaces you custom view (second and third parameters of inflate)
e.g.
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val xmlView = inflater.inflate(R.layout.blue_square, null, false)
Draw this new view to Bitmap
Sorry this is Java but you can convert it to Kotlin as you seem to be using that.
Canvas bitmapCanvas = new Canvas();
Bitmap bitmap = Bitmap.createBitmap(xmlView.getWidth(), xmlView.getHeight(), Bitmap.Config.ARGB_8888);
bitmapCanvas.setBitmap(bitmap);
xmlView.draw(bitmapCanvas);
Then in your onDraw draw this Bitmap to your canvas in the right position (setting top and left location to be below your red square)
Something like
plainPaint = Paint().apply { isAntiAlias = true }
canvas.drawBitmap (bitmap, left, top, plainPaint)
Note this is not exactly tested but it is similar to something I do a lot.
2)Don't invert the normal hierarchy of Views and ViewGroups, don't make your SquareView custom View be the single view your an App draws. Your SquareView custom view should just draw a view big enough to be the square (You should implement onMeasure for your custom view).
Then an App could do in it's xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content">
<SquareView
android:id="#+id/custom_square"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<View
android:id="#+id/blue_square"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#0000FF" />
</LinearLayout>

Related

TextView in a custom view is not switching text color in night mode

Using:
com.google.android.material:material:1.4.0
If I have a layout xml file in a fragment/activity with a TextView:
<TextView
style="#style/TextAppearance.MaterialComponents.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
If I go between light/dark mode the text color switches appropriately.
However if I create a custom view:
class MyView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyle, defStyleRes) { ... }
And that view contains the same textview, the text color does not change in night mode.
I initialize the view from a fragment:
new MyView(this.getActivity().getBaseContext());
I have also tried to directly apply the base theme:
new MyView(this.getActivity().getBaseContext(), null, R.id.AppTheme);
In addition for some strange reason I can work around this issue by creating my own text colors in the appropriate light/dark folders and that picks up the change between light/dark:
<TextView
style="#style/TextAppearance.MaterialComponents.Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/my_text_color" />
Turns out I was passing the wrong context.
When creating the view:
new MyView(this.getActivity().getBaseContext());
Should be:
new MyView(this.getActivity());

How to gray out couple last visible items of [RecyclerView]?

How can I make last visible items of recycler view to have a slight different style. For example, I would like them to appear like this:
As you can see, the last 3 items are grayed out proportionally. I have searched the internet but no success. Any suggestions would be greatly appreciated!
Find out the size of the list you are using to populate the recyler view, this may have been set in the adapter constructor. I'll call it myList.
then in your onBindViewHolder something like:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
....
if (myList.size() - position < 3) {
holder.wordRow.setTextColor(this.mContext.getResources().getColor(R.color.light_gray));
}
Where wordRow is a TextView element
You can create a custom recyclerView layout and then set the topFadingEdgeLength to 0.0. This will allow you to only give effect in the bottom, else it will be applied to the top also.
In your XML
<com.example.BottomFadeEdgeRecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="vertical"
android:fadingEdgeLength="180dp"
android:requiresFadingEdge="vertical">
</com.example.BottomFadeEdgeRecyclerView>
I have set the fadingEdgeLength to 180dp. You can set the desired value
And the custom view class
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.RecyclerView
class BottomFadeEdgeRecyclerView : RecyclerView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun getTopFadingEdgeStrength(): Float {
return 0.0f
}
}
you can create a custom scrollView layout and then set the topFadingEdgeLength to 0.0. This will allow you to only give effect in the bottom, else it will be applied to the top also.
You can use the scrollView to wrap your recyclerView
In the XML set your scrollView as below
<com.example.BottomFadeEdgeScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="vertical"
android:fadingEdgeLength="180dp"
android:requiresFadingEdge="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.example.BottomFadeEdgeScrollView>
I have set the fadingEdgeLength to 180dp. You can set the desired value
Here's the file in Kotlin
import android.content.Context
import android.util.AttributeSet
import android.widget.ScrollView
class BottomFadeEdgeScrollView : ScrollView {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun getTopFadingEdgeStrength(): Float {
return 0.0f
}
}

How to simply create a custom view without duplicate LinearLayout?

The simplest way I found to create a custom view, so I don't have to handle annoying things like to override the onLayout() method, is to make it inherit from a LinearLayout. I also have a LinearLayout at the root of the associated XML file that I inflate, so there is 2 of them at the root.
How can I optimise this, by removing one of this extra LinearLayout, but keep it simple to create custom views ?
MyToolbar.kt:
class MyToolbar #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
LinearLayoutCompat(context, attrs, defStyleAttr) {
private val binding = MyToolbarBinding.inflate(LayoutInflater.from(context), this, true)
init {
// [...] Initialization of my view ...
}
}
my_toolbar.xml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- Actual content of my view -->
</LinearLayout>
What I have right now after #Pawel and #Cata suggestion. This doesn't work, the LinearLayout use the whole height of the parent, but it should only wrap its content.
MyToolbar.kt:
class MyToolbar #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
LinearLayoutCompat(context, attrs, defStyleAttr) {
private val binding
init {
// Tried to add the attributes here as they seems ignored on the `merge` tag
gravity = Gravity.CENTER_VERTICAL
orientation = HORIZONTAL
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
binding = MyToolbarBinding.inflate(LayoutInflater.from(context), this)
// [...] Initialization of the view ...
}
}
my_toolbar.xml:
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- Actual content of the view -->
</merge>
As Pawel suggested you could use merge to do that.
This is a sample from the developer website:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Here you can add your custom content views.. -->
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/add"/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/delete"/>
</merge>

How to decorate the camer preview during barcode scanning?

I want to create a barcode scanner activity, I found this nice tutorial:
http://www.devexchanges.info/2016/10/reading-barcodeqr-code-using-mobile.html
How can I decorate the preview image with detected area (with dots or rectangle).
Put another transparent view on top of your SurfaceView and draw on that. B oth views will loosely coupled, so there is some math involved in getting it right to match the decoration with the camera output.
So in your activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<SurfaceView
android:id="#+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true" />
<com.yourApp.YourDecoratorView
android:id="#+id/decorator_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true" />
...
</RelativeLayout>
And then, create a custom view:
class YourDecoratorView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
... initializing code
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// your code
canvas?.drawLine(0f, 0f, 0f, paint)
}
}
If it is just static visuals you want to show, use an ImageView instead of a custom view and create the drawable you want.

Apply offset to GridLayout items and preserve equal size among all items

I want to create a space between items in the grid. The items should all have equal width/height.
What I've tried:
Create a GridLayoutOffsetDecorator that applies an offset to all grid items:
class GridLayoutOffsetDecorator(var offset: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State?) {
super.getItemOffsets(outRect, view, parent, state)
outRect.set(offset, offset, offset, offset)
}
}
Having an offset of 8dp creates a 16dp space between the items. So we still have to apply 8dp of padding to the outer edges:
<android.support.v7.widget.RecyclerView
android:id="#+id/productList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="8dp" />
The result is:
Problem: the items are not equal in size:
You notice a slight difference in height when you look at the blue line. This difference appears only after padding the recyclerview. This padding seems to slightly resize some of the items. Do you guys have any experience with this problem? Any idea how this can be solved?
By removing the image from the items, the height difference disappears. This is how the image is set:
<SquareImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
app:imageResource="#{product.image}" />
SquareImageView:
class SquareImageView : ImageView {
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
}
}
So the problem may be caused by the image itself.
I had the same problem when working with GridLayoutManager. Solved it by setting width and height for image in adapter.
Here's my solution, hope it help.
That's how I placed ImageView in my layout.
<LinearLayout
android:id="#+id/layoutImageProduct"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
tools:minHeight="100dp"
tools:minWidth="150dp">
<ImageView
android:id="#+id/imageProduct"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:src="#drawable/placeholder_product"/>
</LinearLayout>
And then apply LayoutParams on parent layout and ImageView
//Setting View width/height
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.layoutImageProduct.getLayoutParams();
params.width = (int) ResourcesUtils.getColumnWidth(context);
params.height = ((int) (ResourcesUtils.getColumnWidth(context)));
holder.layoutImageProduct.setLayoutParams(params);
params = (LinearLayout.LayoutParams) holder.imageProduct.getLayoutParams();
params.width = (int) ResourcesUtils.getColumnWidth(context);
params.height = (int) ResourcesUtils.getColumnWidth(context);
holder.imageProduct.setLayoutParams(params);
Here's the getWidth method.
public static float getColumnWidth(Context context) {
if (GRID_COLUMN_WIDTH == -1) {
int screenWidth = getDisplayMetrics(context).widthPixels;
float horizontalPadding = getDimensInPixel(context, R.dimen.activity_horizontal_margin);
// NUM OF COLUMN = 2
GRID_COLUMN_WIDTH = screenWidth / 2 - horizontalPadding * 2;
}
return GRID_COLUMN_WIDTH;
}
See the screenshot, one with placeholder and one with image.
Can you please try with imageview height as fixed.

Categories

Resources