Android Custom Control Implementation Timeline
- 2021-12-12 09:41:46
- OfStack
In this paper, we share the specific code of Android custom control to realize timeline for your reference. The specific contents are as follows
Because there is demand in the project, simply encapsulate one, record one first, and upload it to github when there is time.
1. Add custom attributes first:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="global_TimelineLayout">
<!-- Timeline left offset value -->
<attr name="global_line_margin_left" format="dimension" />
<!-- Offset value on time axis -->
<attr name="global_line_margin_top" format="dimension" />
<!-- Line width -->
<attr name="global_line_stroke_width" format="dimension" />
<!-- Color of line -->
<attr name="global_line_color" format="color" />
<!-- Size of point -->
<attr name="global_point_inner_size" format="dimension" />
<attr name="global_point_out_size" format="dimension" />
<!-- Upper offset value of point -->
<attr name="global_point_margin_top" format="dimension" />
<!-- Color of dots -->
<attr name="global_point_inner_color" format="color" />
<attr name="global_point_out_color" format="color" />
<!-- Icons -->
<attr name="global_icon_src" format="reference" />
<!-- Dashed line -->
<attr name="global_dash_gap" format="dimension" />
<attr name="global_dash_width" format="dimension" />
</declare-styleable>
</resources>
2. Custom Timeline Class:
/**
* Timeline control
* <p>The following snippet shows how to include a linear layout in your layout XML file:</p>
*
* <com.taoche.mars.commonres.widget.TimelineLayout
android:id="@+id/tl_02"
android:layout_width="40dp"
android:layout_height="match_parent"
app:global_line_margin_left="10dp"
app:global_line_margin_top="0dp"
app:global_point_margin_top="10dp"
app:global_point_inner_color="#377CFF"
app:global_point_out_color="#FFE8F0FF"
app:global_point_out_size="8dp"
app:global_point_inner_size="4dp"
app:global_dash_width="8dp"
app:global_dash_gap="2dp"
app:global_line_color="#C9DCFF">
</com.taoche.mars.commonres.widget.TimelineLayout>
*
* <p>The following snippet shows how to java file:</p>
* timelineLayout.setPointMarginTop(10)
timelineLayout.setLineMarginTop(10)
timelineLayout.setPointMarginTop(40)
timelineLayout.setInterrupt(true)
*/
class TimeLinearLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {
private var mContext: Context? = null
private var mLineMarginLeft: Int = 10
private var mLineMarginTop: Int = 0
private var mPointMarginTop: Int = 0
private var mLineStrokeWidth: Int = 2
private var mLineColor: Int = 0
// Radius of inner circle
private var mPointInnerSize: Int = 8
// Cylindrical radius
private var mPointOutSize: Int = 18
// Inner circle color
private var mPointInnerColor: Int = 0
// Cylindrical color
private var mPointOutColor: Int = 0
// Width of dotted line
private var mDashWidth: Int = 0
// Dashed blank width
private var mDashGap: Int = 0
// Whether to interrupt
private var mInterrupt: Boolean = false
private var mIcon: Bitmap? = null
// Line brush
private var mLinePaint: Paint? = null
// Dot brush
private var mPointPaint: Paint? = null
// No. 1 1 Position of points
private var mFirstX = 0
private var mFirstY = 0
// Finally 1 Location of icons
private var mLastX = 0
private var mLastY = 0
init {
setLayerType(View.LAYER_TYPE_SOFTWARE, null) // Turn on hardware acceleration
val ta = context.obtainStyledAttributes(attrs, R.styleable.global_TimelineLayout)
mLineMarginLeft = ta.getDimensionPixelOffset(R.styleable.global_TimelineLayout_global_line_margin_left, 10)
mLineMarginTop = ta.getDimensionPixelOffset(R.styleable.global_TimelineLayout_global_line_margin_top, 0)
mPointMarginTop = ta.getDimensionPixelOffset(R.styleable.global_TimelineLayout_global_point_margin_top, 0)
mLineStrokeWidth = ta.getDimensionPixelOffset(R.styleable.global_TimelineLayout_global_line_stroke_width, 2)
mLineColor = ta.getColor(R.styleable.global_TimelineLayout_global_line_color, -0xc22e5b)
mPointInnerSize = ta.getDimensionPixelSize(R.styleable.global_TimelineLayout_global_point_inner_size, 8)
mPointOutSize = ta.getDimensionPixelSize(R.styleable.global_TimelineLayout_global_point_out_size, 18)
mPointInnerColor = ta.getColor(R.styleable.global_TimelineLayout_global_point_inner_color, -0xc22e5b)
mPointOutColor = ta.getColor(R.styleable.global_TimelineLayout_global_point_out_color, -0x170f01)
mDashGap = ta.getDimensionPixelOffset(R.styleable.global_TimelineLayout_global_dash_gap, 0)
mDashWidth = ta.getDimensionPixelOffset(R.styleable.global_TimelineLayout_global_dash_width, 0)
val iconRes = ta.getResourceId(R.styleable.global_TimelineLayout_global_icon_src, View.NO_ID)
if (iconRes > View.NO_ID) {
val drawable = ContextCompat.getDrawable(context,iconRes) as? BitmapDrawable
if (drawable != null) {
mIcon = drawable.bitmap
}
}
ta.recycle()
setWillNotDraw(false)
initView(context)
}
fun setLineMarginTop(lineMarginTop:Int){
this.mLineMarginTop = lineMarginTop
}
fun setPointMarginTop(pointMarginTop:Int){
this.mPointMarginTop = pointMarginTop
}
fun setInterrupt(interrupt:Boolean){
this.mInterrupt = interrupt
}
private fun initView(context: Context) {
mContext = context
mLinePaint = Paint()
mLinePaint?.apply {
isAntiAlias = true
isDither = true
color = mLineColor
strokeWidth = mLineStrokeWidth.toFloat()
style = Paint.Style.FILL_AND_STROKE
// Dashed line setting
if (mDashGap > 0 && mDashWidth > 0) {
//mLinePaint.setPathEffect(new DashPathEffect(new float[]{20,5}, 20));
pathEffect = DashPathEffect(floatArrayOf(mDashWidth.toFloat(), mDashGap.toFloat()), mDashWidth.toFloat())
}
}
mPointPaint = Paint()
mPointPaint?.apply {
isAntiAlias = true
isDither = true
color = mPointInnerColor
style = Paint.Style.FILL
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawTimeline(canvas)
}
private fun drawTimeline(canvas: Canvas) {
drawBetweenLine(canvas)
drawFirstPoint(canvas)
drawLastIcon(canvas)
}
private fun drawFirstPoint(canvas: Canvas) {
val top = top
mFirstX = paddingLeft + mLineMarginLeft + max(mPointOutSize, mPointInnerSize)
mFirstY = top + paddingTop + mPointMarginTop + max(mPointOutSize, mPointInnerSize)
// Draw the outer ring of a circle
mPointPaint?.color = mPointOutColor
canvas.drawCircle(mFirstX.toFloat(), mFirstY.toFloat(), mPointOutSize.toFloat(), mPointPaint)
// Draw the inner ring of a circle
mPointPaint?.color = mPointInnerColor
canvas.drawCircle(mFirstX.toFloat(), mFirstY.toFloat(), mPointInnerSize.toFloat(), mPointPaint)
}
private fun drawLastIcon(canvas: Canvas) {
/*if (child != null) {
int top = child.getTop();
mLastX = mLineMarginLeft;
mLastY = top + child.getPaddingTop() + mLineMarginTop;
// Draw a picture
canvas.drawBitmap(mIcon, mLastX - (mIcon.getWidth() >> 1), mLastY, null);
}*/
val top = top
mLastX = mLineMarginLeft + paddingLeft
mLastY = top + paddingTop + mLineMarginTop
// Draw a picture
if (mIcon != null) {
canvas.drawBitmap(mIcon, mLastX - (mIcon!!.width shr 1).toFloat(), height - mIcon!!.height.toFloat(), null)
}
}
private fun drawBetweenLine(canvas: Canvas) {
val top = top
mFirstX = paddingLeft + mLineMarginLeft + max(mPointOutSize, mPointInnerSize)
mFirstY = top + paddingTop + mLineMarginTop
mLastX = paddingLeft + mLineMarginLeft + max(mPointOutSize, mPointInnerSize)
mLastY = if(!mInterrupt) {top + paddingTop + mLineMarginTop + height} else mPointMarginTop
// From the beginning point to the last icon, draw 1 Strip line
canvas.drawLine(mFirstX.toFloat(), mFirstY.toFloat(), mLastX.toFloat(), mLastY.toFloat(), mLinePaint)
// Draw a circle
//val y = top + paddingTop + mLineMarginTop + mPointInnerSize
//canvas.drawCircle(mFirstX, y, mPointSize, mPointPaint);
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val mode = MeasureSpec.getMode(widthMeasureSpec)
var measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
val measuredHeight = MeasureSpec.getSize(heightMeasureSpec)
if (mode == MeasureSpec.AT_MOST) {
measuredWidth = paddingLeft + mLineMarginLeft + max(mPointOutSize, mPointInnerSize) * 2
}
setMeasuredDimension(measuredWidth, measuredHeight)
}
}
You can refer to it directly in the layout, as follows:
<com.example.demo1224.TimelineLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:line_margin_left="25dp"
app:line_margin_top="0dp"
app:point_margin_top="10dp"
app:point_inner_color="#377CFF"
app:point_out_color="#FFE8F0FF"
app:point_out_size="8dp"
app:point_inner_size="4dp"
app:dash_width="8dp"
app:dash_gap="2dp"
app:line_color="#C9DCFF"
android:orientation="vertical"
android:background="@android:color/white">
</com.example.demo1224.TimelineLayout>
You can also dynamically set related attributes in the code (related attribute comments, which are explained when customizing attributes):
timelineLayout.setPointMarginTop(10)
timelineLayout.setLineMarginTop(10)
timelineLayout.setPointMarginTop(40)
timelineLayout.setInterrupt(true)
For your reference only!