Drawing a cigarette with basic shapes - android

I'm trying to draw a cigarette with the "ash" view being a percentage of the "smokeable" view. It needs to be a percentage because I am going to use a slider which will change the percentage of the "ash" view.
Here is what I want:
Here is my code:
<LinearLayout
android:id="#+id/cigarette"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_marginBottom="20dp"
android:orientation="horizontal">
<View
android:id="#+id/butt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".25"
android:background="#ff7700" />
<LinearLayout
android:id="#+id/smokeable"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".75"
android:background="#cccccc"
android:orientation="horizontal">
<View
android:id="#+id/ash"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".33"
android:background="#777777"
android:gravity="right" />
</LinearLayout>
</LinearLayout>
In this example I want the ash to be 33% of the smokeable view. I can't figure out why this isn't working. Any help would be appreciated.

I think the easiest cleanest way would be to make a custom View for you "cigarette", it would be composed of filter/smokeable/ash, each part with a different color.
So declare some styleable attributes in, say, attrs.xml
<resources>
<declare-styleable name="CigaretteView">
<attr name="filterColor" format="color" />
<attr name="smokeableColor" format="color" />
<attr name="ashColor" format="color" />
<attr name="filterWidth" format="dimension" />
</declare-styleable>
</resources>
The View class itself:
class CigaretteView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
companion object {
private val DEFAULT_FILTER_COLOR = 0xffbbaa00.toInt()
private val DEFAULT_SMOKABLE_COLOR = 0xffffffff.toInt()
private val DEFAULT_ASH_COLOR = 0xff888888.toInt()
private val DEFAUALT_FILTER_WIDTH_PX = 120
}
private val filterPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val smokeablePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val ashPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var filterWidthPx = DEFAUALT_FILTER_WIDTH_PX
private val rect = Rect()
var smokedPercent = 0f
set(value) {
field = value
invalidate()
}
init {
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.CigaretteView, 0, 0)
var filterColor = DEFAULT_FILTER_COLOR
var smokeableColor = DEFAULT_SMOKABLE_COLOR
var ashColor = DEFAULT_ASH_COLOR
try {
filterColor = a.getColor(R.styleable.CigaretteView_filterColor, filterColor)
smokeableColor = a.getColor(R.styleable.CigaretteView_smokeableColor, smokeableColor)
ashColor = a.getColor(R.styleable.CigaretteView_ashColor, ashColor)
filterWidthPx = a.getDimensionPixelSize(R.styleable.CigaretteView_filterWidth, filterWidthPx)
} finally {
a.recycle()
}
filterPaint.color = filterColor
smokeablePaint.color = smokeableColor
ashPaint.color = ashColor
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
rect.set(0, 0, filterWidthPx, height)
canvas.drawRect(rect, filterPaint)
val leftOver = (width - filterWidthPx).toFloat()
val ashStartPoint = (width - leftOver * smokedPercent).toInt()
rect.set(filterWidthPx, 0, ashStartPoint, height)
canvas.drawRect(rect, smokeablePaint)
rect.set(ashStartPoint, 0, width, height)
canvas.drawRect(rect, ashPaint)
}
}
Your xml layout would be simplified to
<com.example.CigaretteView
android:id="#+id/cigaretteView"
android:layout_width="match_parent"
android:layout_height="48dp"
app:filterWidth="48dp"
app:filterColor="#ffbbaa00"
app:smokeableColor="#ffffffff"
app:ashColor="#ff888888"/>
And then you could simply assign ash percent value with
myCigaretteView.smokedPercent = percent
where percent goes from 0 (cigarette is intact) to 1 (cigarette completely smoked).

The inner LinearLayout needs another child View for the not-yet-consumed part of the cigarette. Its layout_weight has to be 1 - 0.33 = 0.67 in your example (the sum of the weights of both siblings always has to be 1).
<LinearLayout
android:id="#+id/cigarette"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_below="#+id/titleText"
android:orientation="horizontal">
<View
android:id="#+id/butt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".25"
android:background="#ff7700" />
<LinearLayout
android:id="#+id/smokeable"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".75"
android:orientation="horizontal">
<View
android:id="#+id/rest"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="#cccccc"
android:layout_weight=".67"
/>
<View
android:id="#+id/ash"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".33"
android:background="#777777"
android:gravity="right" />
</LinearLayout>
</LinearLayout>
Please note that while this will fix your code, the "nested weighted LinearLayout" approach isn't a good way to go, even if modern devices should not run into performance problems.
The solution by lelloman involving a custom View is much better because you need only one View, not two ViewGroups plus three Views. You have one method to easily change the percentage, plus all the calculating and drawing parts are nicely encapsulated.

Given that all three elements are in line, you only want and need one LinearLayout parent:
<LinearLayout
android:id="#+id/cigarette"
android:layout_width="match_parent"
android:layout_height="20dp"
android:orientation="horizontal">
<View
android:id="#+id/butt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="25"
android:background="#ff7700" />
<View
android:id="#+id/smokeable"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="65"
android:background="#cccccc" />
<View
android:id="#+id/ash"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="10"
android:background="#777777" />
</LinearLayout>

Here's the simplest thing I can think of that does what you need:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="#777777"
android:orientation="horizontal">
<View
android:id="#+id/butt"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.25"
android:background="#ff7700"/>
<View
android:id="#+id/smokeable"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.75"
android:background="#cccccc"
android:transformPivotX="0px"/>
</LinearLayout>
Now you can call smokeable.setScaleX() with any percentage to get the desired result.

Related

Checking if finger is over certain view not working in Android

I am working on a paint app with the following layout:
For the paint app, I detect touch events on the Canvas using onTouchEvent. I have one problem, I want to also detect touch events in which the user begins the swipe on the root and then hovers over the Canvas.
To achieve this, I added the following code:
binding.root.setOnTouchListener { _, motionEvent ->
val hitRect = Rect()
binding.activityCanvasCardView.getHitRect(hitRect)
if (hitRect.contains(motionEvent.rawX.toInt(), motionEvent.rawY.toInt())) {
binding.activityCanvasPixelGridView.onTouchEvent(motionEvent)
}
true
}
It kind of works, but the thing is. It's not detecting the touch events over the canvas (wrapped in a CardView) properly, it's like there's a sort of delay:
XML code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/fragment_background_color_daynight"
tools:context=".activities.canvas.CanvasActivity">
<!-- This view is here to ensure that when the user zooms in, there is no overlap -->
<View
android:elevation="20dp"
android:outlineProvider="none"
android:id="#+id/activityCanvas_topView"
android:layout_width="0dp"
android:layout_height="90dp"
android:background="#color/fragment_background_color_daynight"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- The ColorSwitcherView is a view I created which helps
simplify the code for controlling the user's primary/secondary color -->
<com.therealbluepandabear.pixapencil.customviews.colorswitcherview.ColorSwitcherView
android:id="#+id/activityCanvas_colorSwitcherView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:elevation="20dp"
android:outlineProvider="none"
app:isPrimarySelected="true"
app:layout_constraintEnd_toEndOf="#+id/activityCanvas_topView"
app:layout_constraintTop_toTopOf="#+id/activityCanvas_colorPickerRecyclerView" />
<!-- The user's color palette data will be displayed in this RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:elevation="20dp"
android:outlineProvider="none"
android:id="#+id/activityCanvas_colorPickerRecyclerView"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_topView"
app:layout_constraintEnd_toStartOf="#+id/activityCanvas_colorSwitcherView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/activityCanvas_primaryFragmentHost"
tools:listitem="#layout/color_picker_layout" />
<!-- This FrameLayout is crucial when it comes to the calculation of the TransparentBackgroundView and PixelGridView -->
<FrameLayout
android:id="#+id/activityCanvas_distanceContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_tabLayout"
app:layout_constraintEnd_toEndOf="#+id/activityCanvas_primaryFragmentHost"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/activityCanvas_topView" />
<!-- This gives both views (the PixelGridView and TransparentBackgroundView) a nice drop shadow -->
<com.google.android.material.card.MaterialCardView
android:id="#+id/activityCanvas_cardView"
style="#style/activityCanvas_canvasFragmentHostCardViewParent_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_tabLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/activityCanvas_topView">
<!-- At runtime, the width and height of the TransparentBackgroundView and PixelGridView will be calculated -->
<com.therealbluepandabear.pixapencil.customviews.transparentbackgroundview.TransparentBackgroundView
android:id="#+id/activityCanvas_transparentBackgroundView"
android:layout_width="0dp"
android:layout_height="0dp" />
<com.therealbluepandabear.pixapencil.customviews.pixelgridview.PixelGridView
android:id="#+id/activityCanvas_pixelGridView"
android:layout_width="0dp"
android:layout_height="0dp" />
</com.google.android.material.card.MaterialCardView>
<!-- The primary tab layout -->
<com.google.android.material.tabs.TabLayout
android:elevation="20dp"
android:outlineProvider="none"
android:id="#+id/activityCanvas_tabLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:tabStripEnabled="false"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_viewPager2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_tools_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_filters_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_color_palettes_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_brushes_str" />
</com.google.android.material.tabs.TabLayout>
<!-- This view allows move functionality -->
<View
android:elevation="20dp"
android:outlineProvider="none"
android:id="#+id/activityCanvas_moveView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/transparent"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_distanceContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/activityCanvas_topView" />
<!-- The tools, palettes, brushes, and filters fragment will be displayed inside this ViewPager -->
<androidx.viewpager2.widget.ViewPager2
android:elevation="20dp"
android:outlineProvider="none"
android:id="#+id/activityCanvas_viewPager2"
android:layout_width="0dp"
android:layout_height="110dp"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_primaryFragmentHost"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- This CoordinatorLayout is responsible for ensuring that the app's snackbars can be swiped -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:elevation="20dp"
android:outlineProvider="none"
android:id="#+id/activityCanvas_coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- All of the full page fragments will be displayed in this fragment host -->
<FrameLayout
android:elevation="20dp"
android:outlineProvider="none"
android:id="#+id/activityCanvas_primaryFragmentHost"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
How can I detect touch events properly over a view?
binding.activityCanvasCardView.getHitRect(hitRect) is in the coordinates of the view's parent. See View#getHitRect().
motionEvent.rawX and (), motionEvent.rawY are in the device display coordinates. See MotionEvent#getRawX().
The offset is going to be the difference between the two. You will need to transform one set of coordinates to the other to make the comparisons.
Use MotionEvent#getX() and MotionEvent#getY() for view coordinates.
The other problem that you may have is that since the touch listener is on the root view, the MotionEvent passed to your custom view, PixelGridView, will be in the coordinates of the root view. The custom view would have to have a way to translate those coordinates to its own coordinates to draw on its canvas properly. Maybe you are accommodating this now, but your code for that custom view is not posted.
Update: Sample coode
This is an update to the update with the sample code. Although what I posted before demonstrates the concepts, there were a few things that I thought needed to be corrected for a more complete answer. The following is the updated code.
Let's consider a simplified layout:
<layout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_optimizationLevel="none">
<com.google.android.material.card.MaterialCardView
android:id="#+id/activityCanvas_cardView"
android:layout_width="300dp"
android:layout_height="300dp"
app:cardBackgroundColor="#android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.example.starterapp.MyView
android:id="#+id/activityCanvas_pixelGridView"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_margin="50dp"
android:background="#android:color/holo_blue_light" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And a simple custom view that draws a path:
class MyView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val mPath = Path()
private val mPaint = Paint().apply {
color = context.getColor(android.R.color.black)
style = Paint.Style.STROKE
strokeWidth = 5f
}
private lateinit var mViewOffset: Point
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawPath(mPath, mPaint)
}
fun addMotion(motionEvent: MotionEvent) {
for (i in 0 until motionEvent.historySize) {
addPoint(motionEvent.getHistoricalX(i), motionEvent.getHistoricalY(i))
}
addPoint(motionEvent.x, motionEvent.y)
invalidate()
}
fun startDrawing(motionEvent: MotionEvent) {
mPath.reset()
mPath.moveTo(motionEvent.x - mViewOffset.x, motionEvent.y - mViewOffset.y)
invalidate()
}
fun setViewOffset(offset: Point) {
mViewOffset = Point(offset)
}
private fun addPoint(x: Float, y: Float) {
mPath.lineTo(x - mViewOffset.x, y - mViewOffset.y)
}
}
And, finally a fragment that does the work. Comments are in the code.
class MainFragment : Fragment() {
private lateinit var binding: FragmentMainBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.setContentView(requireActivity(), R.layout.fragment_main)
binding.root.setOnTouchListener { _, motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN ->
binding.activityCanvasPixelGridView.startDrawing(motionEvent)
MotionEvent.ACTION_MOVE ->
binding.activityCanvasPixelGridView.addMotion(motionEvent)
}
true
}
// Wait until everything is laid out so positions and sizes are known.
binding.root.doOnNextLayout {
val gridViewOffset = Point()
var view = binding.activityCanvasPixelGridView as View
while (view != it) {
gridViewOffset.x += view.left
gridViewOffset.y += view.top
view = view.parent as View
}
binding.activityCanvasPixelGridView.setViewOffset(gridViewOffset)
}
return binding.root
}
companion object {
val TAG = this::class.simpleName
}
}
When all this is executed, we see the following:

How to use inset dividers in RecyclerView using AndroidX?

I have read many posts about dividers in RecyclerViews but I did not find any implementation example of an inset dividers as suggested by Material Design:
This picture is taken from https://material.io/components/dividers#types. So I am looking for a divider that is aligned with the text on the left. Can anyone tell me how to implement such a divider using AndroidX?
EDIT:
This is my layout that contains the RecyclerView:
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/offeringsCoordLayout"
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="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/offerings_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
android:visibility="gone" />
<com.turingtechnologies.materialscrollbar.DragScrollBar
android:id="#+id/dragScrollBar"
android:layout_width="wrap_content"
app:msb_recyclerView="#id/offerings_recyclerview"
app:msb_lightOnTouch="false"
app:msb_handleColor="#color/accent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true" />
<include layout="#layout/resourcesview_empty"/>
</RelativeLayout>
<com.leinardi.android.speeddial.SpeedDialOverlayLayout
android:id="#+id/offerings_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:clickable_overlay="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<com.leinardi.android.speeddial.SpeedDialView
android:id="#+id/speedDialOfferings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
app:sdMainFabClosedSrc="#drawable/ic_add_white_24dp"
app:sdMainFabClosedBackgroundColor="#color/accent"
app:sdMainFabOpenedSrc="#drawable/ic_pencil_black"
app:sdMainFabOpenedBackgroundColor="#color/accent"
app:sdMainFabClosedIconColor="#android:color/white"
app:sdMainFabOpenedIconColor="#android:color/white"
app:layout_behavior="#string/speeddial_scrolling_view_snackbar_behavior"
app:sdOverlayLayout="#id/offerings_overlay"/>
Individual row layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/listicon_imageview"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_marginStart="10dp"
android:scaleType="centerInside"
android:clickable="true"
android:src="#drawable/ic_launcher"
android:focusable="true" />
<TextView
android:id="#+id/title_offeringslist_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_toEndOf="#+id/listicon_imageview"
android:layout_toStartOf="#+id/date_offeringlist_item"
android:layout_marginEnd="5dp"
android:layout_alignBaseline="#+id/check_favorite_list"
android:text="Burnt offering"
android:maxLines="1"
android:ellipsize="end"
android:textStyle="bold"
android:textColor="#color/primary_text"
android:textSize="16sp" />
<TextView
android:id="#+id/date_offeringlist_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="#+id/title_offeringslist_item"
android:layout_toStartOf="#+id/check_favorite_list"
android:layout_marginEnd="10dp"
android:maxLines="1"
android:text="Burnt offering"
android:textColor="#color/secondary_text" />
<TextView
android:id="#+id/verses_offeringlist_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/title_offeringslist_item"
android:layout_alignStart="#+id/title_offeringslist_item"
android:layout_toStartOf="#+id/check_favorite_list"
android:ellipsize="end"
android:maxLines="1"
android:textColor="#color/secondary_text"
android:text="verses" />
<TextView
android:id="#+id/experience_offeringslist_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="#+id/verses_offeringlist_item"
android:layout_toStartOf="#+id/check_favorite_list"
android:maxLines="1"
android:layout_below="#+id/verses_offeringlist_item"
android:ellipsize="end"
android:textColor="#color/secondary_text"
android:text="experience" />
<CheckBox
android:id="#+id/check_favorite_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginEnd="10dp"
android:clickable="false"
android:focusable="false"
android:button="#android:drawable/btn_star"/>
<CheckBox
android:id="#+id/check_offered_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/check_favorite_list"
android:layout_alignParentEnd="true"
android:focusable="false"
android:clickable="false"
android:layout_alignBaseline="#+id/experience_offeringslist_item"
android:layout_marginEnd="10dp" />
</RelativeLayout>
You can use a custom RecyclerView.ItemDecoration for that, check code below:
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class InsetDividerItemDecoration(
context: Context,
private val insetDividerLeft: Int) : RecyclerView.ItemDecoration() {
private val attributesArray = intArrayOf(android.R.attr.listDivider)
private var dividerDrawable: Drawable? = null
init {
val typedArray = context.obtainStyledAttributes(attributesArray)
dividerDrawable = typedArray.getDrawable(0)
if (dividerDrawable == null) {
Log.w("InsetDivider", "#android:attr/listDivider was not set in the theme used here")
}
typedArray.recycle()
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
if (parent.layoutManager == null || dividerDrawable == null) {
return
}
val left = parent.paddingLeft + insetDividerLeft
val right = parent.width - parent.paddingRight
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom: Int = top + (dividerDrawable?.intrinsicHeight ?: 0)
dividerDrawable?.setBounds(left, top, right, bottom)
dividerDrawable?.draw(canvas)
}
}
override fun getItemOffsets(
outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State
) = if (dividerDrawable == null) {
outRect.set(0, 0, 0, 0)
} else {
outRect.set(0, 0, 0, dividerDrawable?.intrinsicHeight ?: 0)
}
}
The code above was inspired by DividerItemDecoration.
Now that we have the new custom ItemDecoration, you can add it to the RecyclerView:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
// Ideally you would load the offsets from a dimensions file,
// and that is how you could easily support RTL as well.
recyclerView.addItemDecoration(InsetDividerItemDecoration(view.context, 64.toPx()))
...
}
fun Int.toPx() = (this * Resources.getSystem().displayMetrics.density).toInt()
Edit
I created a Github repo showcasing this solution in action

Unable to match parent the custom dialog's width

I am have created a custom dialog and defined the width as match_parent. But when I ran the application, it appears not even wrapping the content as most of the content is getting clipped. I tried some of the solutions but getting the same result, I have no idea what am I doing wrong:
dialog_messages.xml
<com.google.android.material.card.MaterialCardView
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:orientation="vertical"
app:cardCornerRadius="#dimen/corner"
app:cardElevation="#dimen/elevation"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableLeft="#drawable/ic_msg_sm"
android:drawablePadding="#dimen/normal"
android:drawableTint="#color/colorDanger"
android:padding="#dimen/normal_2x"
android:text="Send Message"
android:textColor="#color/colorBlack"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#color/colorGrey" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="#dimen/normal_2x"
android:text="Tap a message to send"
android:textColor="#color/colorGrey"
android:textSize="#dimen/font_sm1" />
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="#dimen/normal_2x" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="#color/colorGrey" />
<Button
android:id="#+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="#dimen/normal"
android:background="#null"
android:paddingHorizontal="#dimen/normal_4x"
android:text="Cancel"
android:textColor="#color/colorDanger" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
and the setup class
MessageDialog.kt
class MessageDialog(
private val activity: Activity,
private val onMessageClickListener: MessageClickListener
) : Dialog(activity) {
private val messageList = arrayListOf<String>(
"Can't locate you. Send instructions",
"Heavy Traffic. I'll be late 10 minutes",
"Just turning around the block",
"Taxi arrived",
"I am coming to your location",
"I am arriving in 5 minutes",
"Ok"
)
private lateinit var listView: ListView
private lateinit var btnCancel: Button;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(R.layout.dialog_messages)
val adapter = ArrayAdapter(activity, android.R.layout.simple_list_item_1, messageList)
listView = findViewById(R.id.listView)
listView.adapter = adapter
listView.setOnItemClickListener { parent, view, position, id ->
onMessageClickListener.onMessageClick(messageList.get(position))
dismiss()
}
btnCancel = findViewById(R.id.btnCancel)
btnCancel.setOnClickListener {
dismiss()
}
}
}
But this is the result:
I have tried some other answers already provided here, but nothing works.
Create a Dialog object and do this in the show() part.
val dialog = MessageDialog(activity, messageClickListener)
val lp = WindowManager.LayoutParams()
lp.copyFrom(dialog.window!!.attributes)
lp.width = WindowManager.LayoutParams.MATCH_PARENT
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
dialog.show()
val window = dialog.window
window!!.attributes = lp
For my case, i was using afollestad material dialog library, and it had default margin. I overrided that margin on my dimens.xml file
<dimen name="md_dialog_horizontal_margin">8dp</dimen>
Here 8dp means, dialog both from left and right margin will be 16 dp so if you want margin to be 16dp type it as 8dp, divided by two :)

Fit Image in ImageButton in Android

I have 6 ImageButton in my activity, I set images through my code in them ( not using xml).
I want them to cover 75% of the button area. But where as some images cover less area, some are too big to fit into the imageButton. How to programatically resize and show them?
Below is the screen shot
below is the xml-file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginBottom="5sp"
android:layout_marginLeft="2sp"
android:layout_marginRight="5sp"
android:layout_marginTop="0sp" >
<LinearLayout
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="1"
android:orientation="horizontal">
<ImageButton
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
android:id="#+id/button_topleft"
android:layout_marginBottom="5sp"
android:layout_marginLeft="2sp"
android:layout_marginRight="5sp"
android:layout_marginTop="0sp"
/>
<ImageButton
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
android:id="#+id/button_topright"
android:layout_marginBottom="5sp"
android:layout_marginLeft="2sp"
android:layout_marginRight="5sp"
android:layout_marginTop="0sp"
/>
</LinearLayout>
<LinearLayout
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="1"
android:orientation="horizontal">
<ImageButton
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
android:id="#+id/button_repeat"
android:layout_marginBottom="5sp"
android:layout_marginLeft="2sp"
android:layout_marginRight="5sp"
android:layout_marginTop="0sp"
/>
<ImageButton
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
android:id="#+id/button_next"
android:layout_marginBottom="5sp"
android:layout_marginLeft="2sp"
android:layout_marginRight="5sp"
android:layout_marginTop="0sp"
/>
</LinearLayout>
<LinearLayout
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="1"
android:orientation="horizontal">
<ImageButton
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
android:id="#+id/button_bottomleft"
android:layout_marginBottom="5sp"
android:layout_marginLeft="2sp"
android:layout_marginRight="5sp"
android:layout_marginTop="0sp"
/>
<ImageButton
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1"
android:id="#+id/button_bottomright"
android:layout_marginBottom="5sp"
android:layout_marginLeft="2sp"
android:layout_marginRight="5sp"
android:layout_marginTop="0sp"
/>
</LinearLayout>
</LinearLayout>
and a snippet of
myClass.java:
public void addImageButtons()
{
iB_topleft = (ImageButton) findViewById(R.id.button_topleft);
iB_topright = (ImageButton) findViewById(R.id.button_topright);
iB_bottomleft = (ImageButton) findViewById(R.id.button_bottomleft);
iB_bottomright = (ImageButton) findViewById(R.id.button_bottomright);
iB_next = (ImageButton) findViewById(R.id.button_next);
iB_repeat = (ImageButton) findViewById(R.id.button_repeat);
}
public void setImageNextAndRepeat()
{
iB_topleft .setImageResource(R.drawable.aa);
iB_topright.setImageResource(R.drawable.bb);
iB_bottomleft.setImageResource(R.drawable.cc);
iB_bottomright.setImageResource(R.drawable.dd);
iB_next.setImageResource(R.drawable.next);
iB_repeat.setImageResource(R.drawable.repeat);
}
I want them to cover 75% of the button area.
Use android:padding="20dp" (adjust the padding as needed) to control how much the image takes up on the button.
but where as some images cover less area, some are too big to fit into the imageButton. How to programatically resize and show them?
Use a android:scaleType="fitCenter" to have Android scale the images, and android:adjustViewBounds="true" to have them adjust their bounds due to scaling.
All of these attributes can be set in code on each ImageButton at runtime. However, it is much easier to set and preview in xml in my opinion.
Also, do not use sp for anything other than text size, it is scaled depending on the text size preference the user sets, so your sp dimensions will be larger than your intended if the user has a "large" text setting. Use dp instead, as it is not scaled by the user's text size preference.
Here's a snippet of what each button should look like:
<ImageButton
android:id="#+id/button_topleft"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginBottom="5dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="5dp"
android:layout_marginTop="0dp"
android:layout_weight="1"
android:adjustViewBounds="true"
android:padding="20dp"
android:scaleType="fitCenter" />
I'm using the following code in xml
android:adjustViewBounds="true"
android:scaleType="centerInside"
Try to use android:scaleType="fitXY" in i-Imagebutton xml
I'm using android:scaleType="fitCenter" with satisfaction.
Refer below link and try to find what you really want:
ImageView.ScaleType CENTER Center the image in the view, but perform
no scaling.
ImageView.ScaleType CENTER_CROP Scale the image uniformly (maintain
the image's aspect ratio) so that both dimensions (width and height)
of the image will be equal to or larger than the corresponding
dimension of the view (minus padding).
ImageView.ScaleType CENTER_INSIDE Scale the image uniformly (maintain
the image's aspect ratio) so that both dimensions (width and height)
of the image will be equal to or less than the corresponding dimension
of the view (minus padding).
ImageView.ScaleType FIT_CENTER Scale the image using CENTER.
ImageView.ScaleType FIT_END Scale the image using END.
ImageView.ScaleType FIT_START Scale the image using START.
ImageView.ScaleType FIT_XY Scale the image using FILL.
ImageView.ScaleType MATRIX Scale using the image matrix when drawing.
https://developer.android.com/reference/android/widget/ImageView.ScaleType.html
I recently found out by accident that since you have more control on a ImageView that you can set an onclicklistener for an image
here is a sample of a dynamically created image button
private int id;
private bitmap bmp;
LinearLayout.LayoutParams familyimagelayout = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT );
final ImageView familyimage = new ImageView(this);
familyimage.setBackground(null);
familyimage.setImageBitmap(bmp);
familyimage.setScaleType(ImageView.ScaleType.FIT_START);
familyimage.setAdjustViewBounds(true);
familyimage.setId(id);
familyimage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//what you want to do put here
}
});
You can make your ImageButton widget as I did. In my case, I needed a widget with a fixed icon size.
Let's start from custom attributes:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ImageButtonFixedIconSize">
<attr name="imageButton_icon" format="reference" />
<attr name="imageButton_iconWidth" format="dimension" />
<attr name="imageButton_iconHeight" format="dimension" />
</declare-styleable>
</resources>
Widget class is quite simple (the key point is padding calculations in onLayout method):
class ImageButtonFixedIconSize
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.imageButtonStyle
) : ImageButton(context, attrs, defStyleAttr) {
private lateinit var icon: Drawable
#Px
private var iconWidth: Int = 0
#Px
private var iconHeight: Int = 0
init {
scaleType = ScaleType.FIT_XY
attrs?.let { retrieveAttributes(it) }
}
/**
*
*/
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
val width = right - left
val height = bottom - top
val horizontalPadding = if(width > iconWidth) (width - iconWidth) / 2 else 0
val verticalPadding = if(height > iconHeight) (height - iconHeight) / 2 else 0
setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
setImageDrawable(icon)
super.onLayout(changed, left, top, right, bottom)
}
/**
*
*/
private fun retrieveAttributes(attrs: AttributeSet) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageButtonFixedIconSize)
icon = typedArray.getDrawable(R.styleable.ImageButtonFixedIconSize_imageButton_icon)!!
iconWidth = typedArray.getDimension(R.styleable.ImageButtonFixedIconSize_imageButton_iconWidth, 0f).toInt()
iconHeight = typedArray.getDimension(R.styleable.ImageButtonFixedIconSize_imageButton_iconHeight, 0f).toInt()
typedArray.recycle()
}
}
And at last you should use your widget like this:
<com.syleiman.gingermoney.ui.common.controls.ImageButtonFixedIconSize
android:layout_width="90dp"
android:layout_height="63dp"
app:imageButton_icon="#drawable/ic_backspace"
app:imageButton_iconWidth="20dp"
app:imageButton_iconHeight="15dp"
android:id="#+id/backspaceButton"
tools:ignore="ContentDescription"
/>
It worked well in my case. First, you download an image and rename it as iconimage, locates it in the drawable folder. You can change the size by setting android:layout_width or android:layout_height. Finally, we have
<ImageButton
android:id="#+id/answercall"
android:layout_width="120dp"
android:layout_height="80dp"
android:src="#drawable/iconimage"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:scaleType="fitCenter" />

Heterogeneous GridLayout

I am trying to implement the layout below:
I guess GridLayout is suitable for my needs but after 2 hours of struggle I couldn't create even a similar layout.. The layout is resizing itself wrongly, it exceeds the screen of the phone and it also does not span the specified rows and columns.
Here I selected a button so you can see how it exceeds the boundaries:
and here is the associated xml code: https://gist.github.com/2834492
I have reached a similar layout with nested linearlayouts but it's not possible to properly resize it for different screen sizes.
UPDATE - approximate LinearLayout implementation:
The XML code: https://gist.github.com/cdoger/2835887
However, the problem is it does not resize itself properly here some screenshots with different screen configurations:
TLDR: Can someone show me a heterogeneous layout implementation with GridLayout like in the first picture?
The issue you are facing is due to inappropriate use of the GridLayout. The GridLayout is made to show its children in a grid and you are trying to override that without extending the GridLayout. While what you want may be accomplished in code (utilizing numcolumns and columnsize), it will not be useful for multiple screen sizes without a heck of a lot of code.
The only adequate solution that won't require a ton of hacking is judicious use of both LinearLayout and RelativeLayout. LinearLayout should not be used exclusively as it is made to drop items in a line (horizontally or vertically only). This becomes especially apparent when you try and do the bottom four buttons. While the buttons above may be done with LinearLayout with very little effort, RelativeLayout is what you need for the bottom four buttons.
Note:
RelativeLayout can be a little bit tricksy for those with little experience using them. Some pitfalls include: children overlapping, children moving off the screen, height and width rendering improperly applied. If you would like an example, let me know and I will edit my answer.
Final Note:
I'm all for utilizing the current framework objects in unique ways, and genuinely prefer to provide the requested solution. The solution, however, is not viable given the constraints of the question.
(Revision) Solution 1
After some careful thought last night, this may be accomplished with a pure LinearLayout. While I do not like this solution, it should be multi-screen friendly and requires no tooling around from me. Caution should be used with too many LinearLayouts, as according to Google's developers, it can result in slow loading UIs due to the layout_weight property. A second solution utilizing RelativeLayout will be provided when I return home. Now Tested This provides the desired layout parameters on all screen-sizes and orientations.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Button
android:id="#+id/Button01"
android:layout_width="0"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button" />
<Button
android:id="#+id/Button02"
android:layout_width="0"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button" />
</LinearLayout>
<Button
android:id="#+id/button3"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="Button" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.00"
android:orientation="horizontal">
<Button
android:id="#+id/button1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button" />
<Button
android:id="#+id/button2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<Button
android:id="#+id/button4"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="Button" />
<Button
android:id="#+id/button5"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:text="Button" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<Button
android:id="#+id/button6"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:text="Button" />
<Button
android:id="#+id/button7"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="Button" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
Solution 1 Explanation
The key to LinearLayouts is to define your imperatives as separate Layouts and nest the others in them. As you apply constraints to more dimensions, more LinearLayouts must be added to encapsulate the others. For yours, it was crucial to have two more parents in order to maintain the proportion. A great indicator of when you should add another level is when you have to utilize layout_weight using anything other than an integer value. It simply becomes to hard to calculate properly. From there it was relatively simple to break it into columns.
Solution 2 (Failed)
While I was able to achieve desirable results utilizing RelativeLayout and "struts", I could only do so with layouts that were multiples of 2 buttons in height. Such a trick would be awesome as the levels of layout are greatly reduced, so I will work on a pure XML solution and post the answer here if and when I achieve it. In the meantime, the LinearLayout above should suit your needs perfectly.
I read this thread and realised that I wanted a flatter solution than those with linear layout. After some research I ended up making my own layout. It is inspired by a GridLayout but differs a bit.
Please note that if you are going to copy-paste the code you'll need to change package names in some places.
This layout has 4 layout parameters that children use to position themselves.These are layout_left, layout_top, layout_right, layout_bottom. The ICGridLayout itself has two attributes: layout_spacing and columns.
Columns tells the layout how many columns you want it to contain. It will then calculate the size of a cell with the same height as width. Which will be the layouts width/columns.
The spacing is the amount of space you want between each child.
The layout_left|top|right|bottom attributes are the coordinates for each side. The layout does no calculations in order to avoid collision or anything. It just puts the children where they want to be.
If you'd like to have smaller squares you just have to increase the columns attribute.
Keep in mind that this is a quick prototype, I will continue working on it and when I feel that it's ready I'll upload it to Github and put a comment here.
All of my code below should produce the following result:
*****EDIT*****
Added the call to measure for the children, forgot that the first time around.
END EDIT
ICGridLayout.java:
package com.risch.evertsson.iclib.layout;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.risch.evertsson.iclib.R;
/**
* Created by johanrisch on 6/13/13.
*/
public class ICGridLayout extends ViewGroup {
private int mColumns = 4;
private float mSpacing;
public ICGridLayout(Context context) {
super(context);
}
public ICGridLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public ICGridLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(
attrs,
R.styleable.ICGridLayout_Layout);
this.mColumns = a.getInt(R.styleable.ICGridLayout_Layout_columns, 3);
this.mSpacing = a.getDimension(R.styleable.ICGridLayout_Layout_layout_spacing, 0);
a.recycle();
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int width = (int) (r - l);
int side = width / mColumns;
int children = getChildCount();
View child = null;
for (int i = 0; i < children; i++) {
child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
int left = (int) (lp.left * side + mSpacing / 2);
int right = (int) (lp.right * side - mSpacing / 2);
int top = (int) (lp.top * side + mSpacing / 2);
int bottom = (int) (lp.bottom * side - mSpacing / 2);
child.layout(left, top, right, bottom);
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
}
private void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.EXACTLY) {
width = MeasureSpec.getSize(widthMeasureSpec);
} else {
throw new RuntimeException("widthMeasureSpec must be AT_MOST or " +
"EXACTLY not UNSPECIFIED when orientation == VERTICAL");
}
View child = null;
int row = 0;
int side = width / mColumns;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.bottom > row) {
row = lp.bottom;
}
int childHeight = (lp.bottom - lp.top)*side;
int childWidth = (lp.right-lp.left)*side;
int heightSpec = MeasureSpec.makeMeasureSpec(childHeight, LayoutParams.MATCH_PARENT);
int widthSpec = MeasureSpec.makeMeasureSpec(childWidth, LayoutParams.MATCH_PARENT);
child.measure(widthSpec, heightSpec);
}
height = row * side;
// TODO: Figure out a good way to use the heightMeasureSpec...
setMeasuredDimension(width, height);
}
#Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new ICGridLayout.LayoutParams(getContext(), attrs);
}
#Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof ICGridLayout.LayoutParams;
}
#Override
protected ViewGroup.LayoutParams
generateLayoutParams(ViewGroup.LayoutParams p) {
return new ICGridLayout.LayoutParams(p);
}
#Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
int right = 1;
int bottom = 1;
int top = 0;
int left = 0;
int width = -1;
int height = -1;
public LayoutParams() {
super(MATCH_PARENT, MATCH_PARENT);
top = 0;
left = 1;
}
public LayoutParams(int width, int height) {
super(width, height);
top = 0;
left = 1;
}
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(
attrs,
R.styleable.ICGridLayout_Layout);
left = a.getInt(R.styleable.ICGridLayout_Layout_layout_left, 0);
top = a.getInt(R.styleable.ICGridLayout_Layout_layout_top, 0);
right = a.getInt(R.styleable.ICGridLayout_Layout_layout_right, left + 1);
bottom = a.getInt(R.styleable.ICGridLayout_Layout_layout_bottom, top + 1);
height = a.getInt(R.styleable.ICGridLayout_Layout_layout_row_span, -1);
width = a.getInt(R.styleable.ICGridLayout_Layout_layout_col_span, -1);
if (height != -1) {
bottom = top + height;
}
if (width != -1) {
right = left + width;
}
a.recycle();
}
public LayoutParams(ViewGroup.LayoutParams params) {
super(params);
}
}
}
ICGridLayout.java is pretty straight forward. It takes the values provided by the children and lays them out.
attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ICGridLayout_Layout">
<attr name="columns" format="integer"/>
<attr name="layout_left" format="integer"/>
<attr name="layout_top" format="integer"/>
<attr name="layout_right" format="integer"/>
<attr name="layout_bottom" format="integer"/>
<attr name="layout_col_span" format="integer"/>
<attr name="layout_row_span" format="integer"/>
<attr name="layout_spacing" format="dimension"/>
</declare-styleable>
</resources>
example_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.rischit.projectlogger"
android:id="#+id/scroller"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.risch.evertsson.iclib.layout.ICGridLayout
android:id="#+id/ICGridLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_spacing="4dp"
app:columns="4" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_bottom="1"
app:layout_left="0"
app:layout_right="4"
app:layout_top="0"
android:background="#ff0000"
android:text="TextView" />
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_bottom="3"
app:layout_left="3"
app:layout_right="4"
app:layout_top="1"
android:background="#00ff00"
android:text="TextView" />
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_bottom="4"
app:layout_left="0"
app:layout_right="3"
app:layout_top="1"
android:background="#0000ff"
android:text="TextView" />
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_bottom="4"
app:layout_left="3"
app:layout_right="4"
app:layout_top="3"
android:background="#ffff00"
android:text="TextView" />
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_bottom="6"
app:layout_left="0"
app:layout_right="1"
app:layout_top="4"
android:background="#ff00ff"
android:text="TextView" />
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_bottom="6"
app:layout_left="1"
app:layout_right="4"
app:layout_top="4"
android:background="#ffffff"
android:text="TextView" />
</com.risch.evertsson.iclib.layout.ICGridLayout>
</ScrollView>
-- Johan Risch
P.S
This is my first long answer, I've tried to do it in a correct way. If I've failed please tell me without flaming :)
D.S
Like this ?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.54" >
<Button
android:id="#+id/Button01"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.00"
android:text="Button" />
<Button
android:id="#+id/Button02"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.00"
android:text="Button" />
</LinearLayout>
<Button
android:id="#+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="99dp" >
<Button
android:id="#+id/button1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button" />
<Button
android:id="#+id/button2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Button" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<Button
android:id="#+id/button4"
android:layout_width="match_parent"
android:layout_height="152dp"
android:text="Button" />
<Button
android:id="#+id/button5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<Button
android:id="#+id/button6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
<Button
android:id="#+id/button7"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Button" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
As many have said, nested linear layouts seem the only way to win here. Some of the solutions have not used the layout parameters in the most flexible manner. Code below seeks to do that, and in a way that's robust with aspect ratio changes. Details are in the comments.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- First row. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- Equal weights cause two columns of equal width. -->
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="A" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="B" />
</LinearLayout>
<!-- Second row. -->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="C" />
<!-- Third row. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- Equal weights cause two columns of equal width. -->
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="D" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="E" />
</LinearLayout>
<!-- Uneven fourth and fifth rows. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:baselineAligned="false" >
<!-- Left column. Equal weight with right column gives them equal width. -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<!--
The use of weights below assigns all extra space to G. There
are other choices. LinearLayout computes sizes along its
axis as given, then divides the remaining extra space using
weights. If a component doesn't have a weight, it keeps
the specified size exactly.
-->
<!-- Fill width of layout and use wrap height (because there's no weight). -->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="F" />
<!-- Fill width of layout and put all the extra space here. -->
<Button
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="G" />
</LinearLayout>
<!-- Right column. Equal weight with left column gives them equal width. -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical" >
<!-- Same as above except top button gets all the extra space. -->
<Button
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="H" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="I" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
So here is the solution I promised after one year =)
It basically uses the ViewTreeObserver to get the dimensions of the parent layout and create custom views accordingly. Since this code is one year old ViewTreeObserver might not be the best way to get the dimensions dynamically.
You can find the full source code here:
https://github.com/cdoger/Android_layout
I divided the screen into 8 equal widths and 6 equal heights. Here is a snapshot of how I laid out the views:
final RelativeLayout mainLayout = (RelativeLayout) findViewById(R.id.main_layout);
ViewTreeObserver vto = mainLayout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
final int oneUnitWidth = mainLayout.getMeasuredWidth() / 8;
final int oneUnitHeight = mainLayout.getMeasuredHeight() / 6;
/**
* 1
***************************************************************/
final RelativeLayout.LayoutParams otelParams = new RelativeLayout.LayoutParams(
oneUnitWidth * 4, oneUnitHeight);
otelParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
otelParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
// otelParams.setMargins(0, 0, 2, 0);
View1.setLayoutParams(otelParams);
/***************************************************************/
/**
* 2
***************************************************************/
final RelativeLayout.LayoutParams otherParams = new RelativeLayout.LayoutParams(
oneUnitWidth * 4, oneUnitHeight);
otherParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
otherParams.addRule(RelativeLayout.RIGHT_OF, View1.getId());
otherParams.setMargins(2, 0, 0, 0);
View2.setLayoutParams(otherParams);
/***************************************************************/
//... goes on like this
Here is the final screenshot:
Embed your GridLayout in LinearLayout as below and try it worked for me.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnCount="2" >
<Button
android:layout_column="0"
android:layout_columnSpan="1"
android:layout_gravity="start|end"
android:layout_row="0"
android:text="ASDFASDF" />
<Button
android:layout_column="1"
android:layout_gravity="start|end"
android:layout_row="0"
android:text="SDAVDFBDFB" />
<Button
android:layout_column="0"
android:layout_columnSpan="2"
android:layout_gravity="fill|center"
android:layout_row="1"
android:text="ASDVADFBFDAFEW" />
<Button
android:layout_column="0"
android:layout_columnSpan="1"
android:layout_gravity="fill|center"
android:layout_row="2"
android:text="FWEA AWFWEA" />
<Button
android:layout_column="1"
android:layout_columnSpan="1"
android:layout_gravity="fill"
android:layout_row="2"
android:text="BERWEfasf" />
<Button
android:layout_width="94dp"
android:layout_column="0"
android:layout_columnSpan="1"
android:layout_gravity="fill|center"
android:layout_row="3"
android:text="SDFVBFAEVSAD" />
<Button
android:layout_column="0"
android:layout_columnSpan="1"
android:layout_gravity="fill|center"
android:layout_row="4"
android:layout_rowSpan="2"
android:text="GVBAERWEFSD" />
<Button
android:layout_column="1"
android:layout_columnSpan="1"
android:layout_gravity="fill|center"
android:layout_row="3"
android:layout_rowSpan="2"
android:text="VSDFAVE SDFASDWA SDFASD" />
<Button
android:layout_column="1"
android:layout_columnSpan="1"
android:layout_gravity="fill|center"
android:layout_row="5"
android:text="FWEWEGAWEFWAE"/>
</GridLayout>
</LinearLayout>

Categories

Resources