<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">自定義ViewGroup——圓形排列LinearLayout</span>
官方的控件遠遠無法滿足客戶的需求了,很多時候需要自己去定義需要的View和ViewGroup。前段時間因為公司的項目非常趕,自己寫好的東西都沒有時間記錄下來,現在稍微好點,就在部落格這裡留下點足迹。之前一直對onMeasure(),onLayout(),onDraw()這幾個方法似懂非懂的,如今順便複習一下。
如題,我們需要實作一個将ViewGroup内的ChildView進行環形平均排列,效果如下:
自定義ViewGroup步驟:
1、attr.xml自定義屬性
2、自定義ViewGroup,并在構造方法中取得屬性
3、複寫測量控件大小方法onMeasure(),複寫測量擺放ChildView方法onLayout()
4、在布局中使用自定義ViewGroup,并使用自己的自定義屬性(
在根布局中聲命名空間 xmlns:kubyattr="http://schemas.android.com/apk/res/com.happy.myfragmentapplication"
并使用happyattr:circleRadius="70dp"
)
下面上代碼:
1、attr.xml自定義屬性
<declare-styleable name="CircleLinearLayout">
<attr name="circleRadius"/>
</declare-styleable>
2、自定義ViewGroup,并在構造方法中取得屬性
3、複寫測量控件大小方法onMeasure(),複寫測量擺放ChildView方法onLayout()
package com.happy.myfragmentapplication.customview;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.happy.myfragmentapplication.R;
import com.happy.myfragmentapplication.Utils.MyMath;
import java.util.List;
/**
* Created by Kuby on 2016/5/20.
*/
public class CircleLinearLayout extends ViewGroup{
private final String TAG = "CircleLinearLayout";
private int radius;
public CircleLinearLayout(Context context) {
super(context);
}
public CircleLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.CircleLinearLayout,defStyleAttr,0);
int count = typedArray.getIndexCount();
for (int i = 0; i<count; i++){
int attNameId = typedArray.getIndex(i);
switch (attNameId){
case R.styleable.CircleLinearLayout_circleRadius:
radius = typedArray.getDimensionPixelSize(attNameId,10);
break;
}
}
typedArray.recycle();
Log.d(TAG, "radius = " + radius);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
// return super.generateLayoutParams(attrs);
return new MarginLayoutParams(getContext(),attrs);
}
/**
* 計算所有ChildView的寬度和高度,然後根據ChildView的計算結果設定自己的寬度和高度
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 擷取此ViewGroup上級容器為其推薦計算模式
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
/**
* 擷取此ViewGroup上級容器為其推薦的寬和高
*/
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//計算出所有childView的寬和高,(通過ViewGroup的measureChildren方法為其所有的孩子設定寬和高,此行執行完成後,childView的寬和高都已經正确的計算過了)
measureChildren(widthMeasureSpec, heightMeasureSpec);
/**
* ViewGroup内子控件的寬度和高度
*/
int widthContent = 0;
int heightContent = 0;
int itemHeight =getChildAt(0).getMeasuredHeight();//單個childView的高度
heightContent = (itemHeight+radius)*2;
widthContent = (itemHeight+radius)*2;
Log.d(TAG + "onMeasure", "heightContent:"+heightContent);
/**
* 測量ViewGroup的寬高,如果為wrap_content就按照内容計算得到的寬高
*/
setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : widthContent, (heightMode == MeasureSpec.EXACTLY) ? heightSize : heightContent);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// drawInHorizontal();
drawInCircle();
}
/**
* 按照Circle的方式排列
*/
private void drawInCircle() {
int cCount = getChildCount();
int lastW = 0;
//圓心坐标
float[] circleCentre = {getWidth()/2*1.0f, getHeight()/2*1.0f};
//每個占多少個弧度
// float oItem = 360/cCount*1.0f;
float oItem = (float) (2*Math.PI/cCount*1.0f);
//cCount個坐标
float[][] xyPosition = new float[cCount][2];
for (int i=0; i<cCount; i++)
{
xyPosition[i] = MyMath.getXYPoint(circleCentre,radius,oItem*(i));
//x坐标
int xLabel = (int) xyPosition[i][0];
//y坐标
int yLabel = (int) xyPosition[i][1];
Log.d(TAG, "position : (" + xLabel + "," + yLabel + ")");
View view = getChildAt(i);
view.layout((int) (xLabel - view.getMeasuredWidth() / 2 * 1.0f), (int) (yLabel - view.getMeasuredHeight() / 2 * 1.0f), (int) (xLabel + view.getMeasuredWidth() / 2 * 1.0f), (int) (yLabel + view.getMeasuredHeight() / 2 * 1.0f));
}
}
/**
* 按照horizontal的方式排列
*/
private void drawInHorizontal() {
int cCount = getChildCount();
int lastW = 0;
for (int i=0; i < cCount; i++){
View view = getChildAt(i);
view.layout(lastW,0,view.getWidth(),view.getHeight());
lastW += view.getWidth();
Log.i(TAG, "lastW = " + lastW);
}
}
}
還有一個計算坐标的工具類
package com.happy.myfragmentapplication.Utils;
import android.util.Log;
/**
* Created by Kuby on 2016/5/20.
*/
public class MyMath {
private final static String TAG = "MyMath";
/**
* 以原點為圓點,以radius維半徑畫圓,通過弧度o,獲得坐标
* @param radius 半徑
* @param o 弧度
* @return
*/
public static float[] getXYPoint(float[] centrePoint, int radius, float o){
Log.d(TAG,"o: "+o);
Log.d(TAG,"radius: "+radius);
Log.d(TAG,"centrePoint: ["+centrePoint[0]+","+centrePoint[1]+"]");
float[] xyPoint = {(float) (radius*Math.sin(o) + centrePoint[0]), (float) ((-1)*radius*Math.cos(o) + centrePoint[1])};
// Log.d(TAG,"test: ["+xyPoint[0]+","+xyPoint[1]+"]");
return xyPoint;
}
}
4、在布局中使用自定義ViewGroup,并使用自己的自定義屬性
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:happyattr="http://schemas.android.com/apk/res/com.happy.myfragmentapplication"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.happy.myfragmentapplication.FragmentContainer1">
<!-- TODO: Update blank fragment layout -->
<TextView android:layout_width="match_parent" android:layout_height="match_parent"
android:text="Fragment Container 2" />
<RelativeLayout
android:id="@+id/fragment_container"
android:background="@android:color/holo_green_dark"
android:layout_margin="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.happy.myfragmentapplication.customview.CircleLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/customvolum"
happyattr:circleRadius="70dp"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="tv1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="tv2"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="tv3"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="tv4"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="tv5"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="tv6"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="tv7"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="tv8"/>
</com.happy.myfragmentapplication.customview.CircleLinearLayout>
</RelativeLayout>
</FrameLayout>