aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/ca/allanwang/kau/ui/views/MeasureSpecDelegate.kt
blob: 6481306c26cf97545c7aaf5f2d5d544a11a568f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
 * Copyright 2018 Allan Wang
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ca.allanwang.kau.ui.views

import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import ca.allanwang.kau.R
import ca.allanwang.kau.utils.parentViewGroup

/**
 * Created by Allan Wang on 2017-07-14.
 *
 * Handles relative sizes for any view
 * You may delegate all methods to [MeasureSpecDelegate]
 * and call the two methods: [initAttrs] and [onMeasure]
 */
interface MeasureSpecContract {

    /**
     * Width will be calculated as a percentage of the parent
     * This takes precedence over relativeWidth
     */
    var relativeWidthToParent: Float
    /**
     * Height will be calculated as a percentage of the parent
     * This takes precedence over relativeHeight
     */
    var relativeHeightToParent: Float
    /**
     * Width will be calculated based on the measured height
     */
    var relativeWidth: Float
    /**
     * Height will be calculated based on the measure width
     */
    var relativeHeight: Float
    /**
     * Width will be once again calculated from the current measured height
     * This is the last step
     */
    var postRelativeWidth: Float
    /**
     * Height will be once again calculated from the current measured width
     * This is the last step
     */
    var postRelativeHeight: Float

    /**
     * Retrieves relative values from the [AttributeSet]
     * Call this on init
     */
    fun initAttrs(context: Context, attrs: AttributeSet?)

    /**
     * Calculates the final measure specs
     * Call this from [View.onMeasure] and send the Pair result as the specs
     * The pair is of the format (width, height)
     *
     * Example:
     * <pre>
     * {@code
     * override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
     *     val result = onMeasure(this, widthMeasureSpec, heightMeasureSpec)
     *     super.onMeasure(result.first, result.second)
     * }
     * }
     * </pre>
     */
    fun onMeasure(view: View, widthMeasureSpec: Int, heightMeasureSpec: Int): Pair<Int, Int>
}

class MeasureSpecDelegate : MeasureSpecContract {

    override var relativeWidth = -1f
    override var relativeHeight = -1f
    override var relativeWidthToParent = -1f
    override var relativeHeightToParent = -1f
    override var postRelativeWidth: Float = -1f
    override var postRelativeHeight: Float = -1f
    private val parentFrame = Rect()

    override fun initAttrs(context: Context, attrs: AttributeSet?) {
        if (attrs == null) return
        val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MeasureSpecDelegate)
        relativeWidth = styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_relativeWidth, relativeWidth)
        relativeHeight = styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_relativeHeight, relativeHeight)
        relativeWidthToParent =
            styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_relativeWidthToParent, relativeWidthToParent)
        relativeHeightToParent =
            styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_relativeHeightToParent, relativeHeightToParent)
        postRelativeWidth = styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_postRelativeWidth, postRelativeWidth)
        postRelativeHeight =
            styledAttrs.getFloat(R.styleable.MeasureSpecDelegate_postRelativeHeight, postRelativeHeight)
        styledAttrs.recycle()
    }

    override fun onMeasure(view: View, widthMeasureSpec: Int, heightMeasureSpec: Int): Pair<Int, Int> {
        view.parentViewGroup.getWindowVisibleDisplayFrame(parentFrame)
        var width = View.MeasureSpec.getSize(widthMeasureSpec).toFloat()
        var height = View.MeasureSpec.getSize(heightMeasureSpec).toFloat()
        //first cycle - relative to parent
        if (relativeHeightToParent > 0)
            height = relativeHeightToParent * parentFrame.height()
        if (relativeWidthToParent > 0)
            width = relativeWidthToParent * parentFrame.width()
        //second cycle - relative to each other
        if (relativeHeight > 0)
            height = relativeHeight * width
        else if (relativeWidth > 0)
            width = relativeWidth * height
        //third cycle - relative to each other
        if (postRelativeHeight > 0)
            height = postRelativeHeight * width
        else if (postRelativeWidth > 0)
            width = postRelativeWidth * height
        return Pair(width.measureSpec, height.measureSpec)
    }

    private val Float.measureSpec: Int
        get() = View.MeasureSpec.makeMeasureSpec(this.toInt(), View.MeasureSpec.EXACTLY)
}