android實現八大行星繞太陽3D旋轉效果
仿上面效果,採用kotlin實現,邏輯要簡單些,註釋在源碼中,一看就懂
<com.example.androidxdemo.star.StarGroupView
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<!-- 增加太陽View -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_background"
android:tag="center" />
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:text="1" />
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="2" />
<TextView
android:id="@+id/tv3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_green_dark"
android:gravity="center"
android:text="3" />
<TextView
android:id="@+id/tv4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_dark"
android:gravity="center"
android:text="4" />
<TextView
android:id="@+id/tv5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_green_light"
android:gravity="center"
android:text="5" />
<TextView
android:id="@+id/tv6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_orange_light"
android:gravity="center"
android:text="6" />
<TextView
android:id="@+id/tv7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff3311"
android:gravity="center"
android:text="7" />
<TextView
android:id="@+id/tv8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#11aa44"
android:gravity="center"
android:text="8" />
<TextView
android:id="@+id/tv9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff99cc"
android:gravity="center"
android:text="9" />
</com.example.androidxdemo.star.StarGroupView>
class StarGroupView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
val TAG_CENTER = "center"
//控件寬高
var mWidth = 0
var mHeight = 0
//中心點
var centerWidth = 0
var centerHeight = 0
private var mRadius = 0
//子View的大小,默認是1:1正方形
private var childWidth = 0
private var childHeight = 0
//初始角度,掃過的角度
var sweepAngle = 90
//值越大,轉動越快
val changedAngle = 2
//延時delayTime ms,進行一次值的改變
val delayTime = 100L
//手指按下時x和角度
private var downX = 0f
private var downAngle = 0
//延時改變view位置和繪製順序
private val autoScrollRunnable = object : Runnable {
override fun run() {
sweepAngle = (sweepAngle + changedAngle) % 360
layoutChildren()
postDelayed(this, delayTime)
}
}
init {
postDelayed(autoScrollRunnable, delayTime)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
// "w: $w h: $h oldw: $oldw oldh: $oldh".log()
//如果寬度大於兩倍高度,則高度決定寬度,寬度 = 2 * 高度
//否則,寬度小於等於兩倍高度,則寬度決定高度,高度 = 1/2 * 寬度
if (w > h * 2) {
mHeight = h
mWidth = mHeight * 2
} else {
mWidth = w
mHeight = mWidth / 2
}
//確定中心點的位置,childWidth = heightWidth = mWidth / 6
centerWidth = mWidth / 2
centerHeight = mHeight / 2
childWidth = mWidth / 6
childHeight = childWidth
//半徑,
mRadius = mHeight - childWidth
"w: $w h: $h oldw: $oldw oldh: $oldh mWidth: $mWidth mHeight: $mHeight mRadius: $mRadius childWidth: $childWidth".log()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
// "changed $changed left: $left top: $top right: $right bottom: $bottom".log()
layoutChildren()
}
/**
* 計算view的位置
* z值範圍(0-1)
* 改變子View的z值以改變子View的繪製優先級,z越大優先級越低(最後繪製)
*/
private fun layoutChildren() {
val centerView = findViewWithTag<View>(TAG_CENTER)
val degree: Float
if (centerView == null) {
degree = 360f / childCount
} else {
//中心點view的寬高爲 2 * childWith
degree = 360f / (childCount - 1)
centerView.layout(
centerWidth - childWidth, centerHeight - childHeight,
centerWidth + childWidth, centerHeight + childHeight
)
}
for (index in 0 until childCount) {
val child = getChildAt(index)
if (child.tag == TAG_CENTER) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
child.z = 0.5f
}
child.rotation = sweepAngle.toFloat()
// "中心旋轉 rotation: $sweepAngle".log()
continue
}
//設置child的大小尺寸和佈局, index爲0的是centerView
val radius = (degree * index + sweepAngle) * Math.PI / 180
val childCenterX = (mRadius * cos(radius)).toInt()
val childCenterY = (mRadius * sin(radius) / 2).toInt()
// "index:$index degree:${degree * (index - 1)} radius:$radius childCenterX: $childCenterX childCenterY: $childCenterY".log()
val left = childCenterX - childWidth / 2 + centerWidth
val top = childCenterY - childHeight / 2 + centerHeight
val right = childCenterX + childWidth / 2 + centerWidth
val bottom = childCenterY + childHeight / 2 + centerHeight
child.layout(left, top, right, bottom)
val scale = ((sin(radius) + 2) / 3f).toFloat()
child.scaleX = scale
child.scaleY = scale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
child.z = scale
}
// "index:$index child left: $left top: $top right: $right bottom: $bottom child.scaleX: ${child.scaleX}".logW()
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
downX = event.x
downAngle = sweepAngle
removeCallbacks(autoScrollRunnable)
"actionDown downX $downX downAngle: $downAngle ".logW()
return true
}
MotionEvent.ACTION_MOVE -> {
val dx = event.x - downX
if (dx != 0f) {
sweepAngle = (dx * 0.2 + downAngle).toInt()
"actionMove dx $dx sweepAngle: $sweepAngle ".log()
layoutChildren()
}
}
MotionEvent.ACTION_UP -> {
"actionUp ".logW()
postDelayed(autoScrollRunnable, 16)
}
}
return super.onTouchEvent(event)
}
}