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)
}
}