自定義ViewGroup排版家譜樹成員

  1. 完整代碼
/**
 * Created by wanggang on 2017/4/20.
 * 家族樹
 */

public class FamilyTreeView extends ViewGroup {

    /**
     * 界面可滾動的左邊界
     */
    private int leftBorder;

    /**
     * 界面可滾動的上邊界
     */
    private int topBorder;

    /**
     * 界面可滾動的右邊界
     */
    private int rightBorder;

    /**
     * 界面可滾動的下邊界
     */
    private int bottomBorder;

    FamilyTreeAdapter familyTreeAdapter;
    private Paint mPaint;

    int colSpace;
    int lineSpace;
    int radius; // 弧形的半徑
    int itemWidth;
    int itemHeight;

    List<Ponit> ponits; // 記錄所有有軌跡的點
    ValueAnimator mAnimator;
    float mPercent;

    Stack<PersonView> personViews; // 保存PersonView

    boolean canLayout; // 是否正在添加View
    boolean canMeasure; // 是否正在添加View

    int layoutIndex;

    public FamilyTreeAdapter getFamilyTreeAdapter() {
        return familyTreeAdapter;
    }

    public void setFamilyTreeAdapter(FamilyTreeAdapter familyTreeAdapter) {
        this.familyTreeAdapter = familyTreeAdapter;
    }

    public FamilyTreeView(Context context) {
        super(context);
        init();
    }

    public FamilyTreeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public FamilyTreeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {

        personViews = new Stack<>();

        colSpace = MeasureUtils.dip2px(getContext(), 24);
        lineSpace = MeasureUtils.dip2px(getContext(), 32);
        radius = MeasureUtils.dip2px(getContext(), 6);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(MeasureUtils.dip2px(getContext(), 1));
        mPaint.setColor(Color.parseColor("#999999"));
        mPaint.setAntiAlias(true);
        setWillNotDraw(false);

        mAnimator = ValueAnimator.ofFloat(0, 1);
        mAnimator.setDuration(1000);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mPercent = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (canMeasure) {
            canMeasure = false;
            for (int i = 0; i < getChildCount(); i++) {
                PersonView childView = (PersonView) getChildAt(i);
                measureChild(childView, widthMeasureSpec, heightMeasureSpec);
                measureView(childView);
            }
//            setMeasuredDimension(rightBorder - leftBorder, bottomBorder - topBorder);
        }
    }



    private void measureView(PersonView personView) {
        PersonEntity personEntity = personView.getPersonEntity();
        PersonData personData = personEntity;
        itemWidth = personView.getMeasuredWidth();
        itemHeight = personView.getMeasuredHeight();
        changeMeasurePoint(personData.getCenterPoint());
        int centerX = personData.getCenterPoint().x;
        int centerY = personData.getCenterPoint().y;
        if (centerX - itemWidth < leftBorder) {
            leftBorder = centerX - itemWidth;
        }
        if (centerX + itemWidth > rightBorder) {
            rightBorder = centerX + itemWidth;
        }
        if (centerY - itemHeight < topBorder) {
            topBorder = centerY - itemHeight;
        }
        if (centerY + itemHeight > bottomBorder) {
            bottomBorder = centerY + itemHeight;
        }
    }

    public void changeMeasurePoint(Ponit ponit) {
        ponit.x = ponit.coordinateX * (itemWidth + colSpace);
        ponit.y = - ponit.coordinateY * (itemHeight + lineSpace);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (canLayout) {
            canLayout = false;

            for (int i = 0; i < getChildCount(); i++) {
                PersonView personView = (PersonView) getChildAt(i);
                layoutView(personView);

                if (i == 0) {
                    personView.setSelected(true);
                    personView.setTitleColor(R.color.white);
                } else {
                    personView.setSelected(false);
                    personView.setTitleColor(R.color.black);
                }
            }
            mAnimator.start();
        }
    }

    /**
     * 通過中心點位置放置view
     */
    private void layoutView(PersonView personView) {
        PersonEntity personEntity = personView.getPersonEntity();
        PersonData personData = personEntity;
        itemWidth = personView.getMeasuredWidth();
        itemHeight = personView.getMeasuredHeight();
        changePoint(personData.getCenterPoint());
        addDrawPoint(personData.getCenterPoint());
        int centerX = personData.getCenterPoint().x;
        int centerY = personData.getCenterPoint().y;
        personView.layout(centerX - itemWidth / 2, centerY - itemHeight / 2,
                centerX + itemWidth / 2, centerY + itemHeight / 2);
        personView.setImage(personData.gender, personData.avatar);
        personView.setTitle(personData.name);
    }

    // 坐標(biāo)系坐標(biāo)轉(zhuǎn)換成屏幕坐標(biāo)
    public void changePoint(Ponit ponit) {
        ponit.x = ponit.x - leftBorder;
        ponit.y = ponit.y - topBorder;
    }

    private void addDrawPoint(Ponit ponit) {
        if (ponits == null) {
            ponits = new LinkedList<>();
        }
        ponits.add(ponit);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (ponits != null) {
            for (int i = 0; i < ponits.size(); i++) {
                Ponit ponit = ponits.get(i);
                if (ponit.connecPonit != null) {
                    ponit.drawConnection(itemWidth, itemHeight, lineSpace, colSpace, radius);
                    canvas.drawPath(ponit.getSegment(mPercent), mPaint);
                }
            }
        }
    }

    public void refreshUI() {
        for (int i = 0; i < getChildCount(); i++) {
            if (getChildAt(i) instanceof PersonView)
                personViews.add((PersonView) getChildAt(i));
        }
        removeAllViews();
        if (ponits != null) {
            ponits.clear();
        }
        int childSize = familyTreeAdapter.dataList.size();

        for (int i = 0; i < childSize; i++) {
            PersonView personView = familyTreeAdapter.getPersonView(getPersonView(), this, familyTreeAdapter.dataList.get(i));
//            addViewInLayout(personView, i, new LayoutParams(DensityUtil.dip2px(getContext(), 60), DensityUtil.dip2px(getContext(), 80)));
            addView(personView);
        }
        canLayout = true;
        canMeasure = true;
        requestLayout();
    }

    private PersonView getPersonView() {
        if (personViews != null && personViews.size() > 0)
            return personViews.pop();
        return null;
    }

    @Override
    public void addView(View child) {
        super.addView(child);
    }

    @Override
    protected boolean addViewInLayout(View child, int index, LayoutParams params, boolean preventRequestLayout) {
        return super.addViewInLayout(child, index, params, preventRequestLayout);
    }

    public int getLeftBorder() {
        return leftBorder;
    }

    public int getTopBorder() {
        return topBorder;
    }

    public int getRightBorder() {
        return rightBorder;
    }

    public int getBottomBorder() {
        return bottomBorder;
    }
}
  1. 添加成員view
public void refreshUI() {
        for (int i = 0; i < getChildCount(); i++) {
            if (getChildAt(i) instanceof PersonView)
                personViews.add((PersonView) getChildAt(i));
        }
        removeAllViews();
        if (ponits != null) {
            ponits.clear();
        }
        int childSize = familyTreeAdapter.dataList.size();

        for (int i = 0; i < childSize; i++) {
            PersonView personView = familyTreeAdapter.getPersonView(getPersonView(), this, familyTreeAdapter.dataList.get(i));
//            addViewInLayout(personView, i, new LayoutParams(DensityUtil.dip2px(getContext(), 60), DensityUtil.dip2px(getContext(), 80)));
            addView(personView);
        }
        canLayout = true;
        canMeasure = true;
        requestLayout();
    }

這段代碼主要工作就是通過adapter中列表的數(shù)據(jù)权悟,往viewgroup中添加成員view。這里使用了canLayout和canMeasure來控制viewgroup的測量和layout次數(shù)推盛。之前使用了addViewInLayout來添加view峦阁,但是當(dāng)viewgroup被縮放的時候,所有添加的子view都不見了耘成,所以使用addView加控制變量來操作榔昔。

  1. 測量以及坐標(biāo)轉(zhuǎn)換
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (canMeasure) {
        canMeasure = false;
        for (int i = 0; i < getChildCount(); i++) {
            PersonView childView = (PersonView) getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            measureView(childView);
        }
    }
}

private void measureView(PersonView personView) {
    PersonEntity personEntity = personView.getPersonEntity();
    PersonData personData = personEntity;
    itemWidth = personView.getMeasuredWidth();
    itemHeight = personView.getMeasuredHeight();
    changeMeasurePoint(personData.getCenterPoint());
    int centerX = personData.getCenterPoint().x;
    int centerY = personData.getCenterPoint().y;
    if (centerX - itemWidth < leftBorder) {
        leftBorder = centerX - itemWidth;
    }
    if (centerX + itemWidth > rightBorder) {
        rightBorder = centerX + itemWidth;
    }
    if (centerY - itemHeight < topBorder) {
        topBorder = centerY - itemHeight;
    }
    if (centerY + itemHeight > bottomBorder) {
        bottomBorder = centerY + itemHeight;
    }
}

public void changeMeasurePoint(Ponit ponit) {
    ponit.x = ponit.coordinateX * (itemWidth + colSpace);
    ponit.y = - ponit.coordinateY * (itemHeight + lineSpace);
}

這里的主要功能是將每個成員節(jié)點的坐標(biāo)系坐標(biāo)轉(zhuǎn)換成屏幕的物理坐標(biāo),其轉(zhuǎn)換規(guī)則就是changeMeasurePoint方法瘪菌。轉(zhuǎn)換成功之后再計算當(dāng)前viewgroup的邊界撒会,也就是leftBorder、rightBorder师妙、topBorder诵肛、bottomBorder的重新賦值。

  1. 成員view的排版布局
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (canLayout) {
        canLayout = false;
        for (int i = 0; i < getChildCount(); i++) {
            PersonView personView = (PersonView) getChildAt(i);
            layoutView(personView);
        }
    }
}

/**
 * 通過中心點位置放置view
 */
private void layoutView(PersonView personView) {
    PersonEntity personEntity = personView.getPersonEntity();
    PersonData personData = personEntity;
    itemWidth = personView.getMeasuredWidth();
    itemHeight = personView.getMeasuredHeight();
    changePoint(personData.getCenterPoint());
    addDrawPoint(personData.getCenterPoint());
    int centerX = personData.getCenterPoint().x;
    int centerY = personData.getCenterPoint().y;
    personView.layout(centerX - itemWidth / 2, centerY - itemHeight / 2,
            centerX + itemWidth / 2, centerY + itemHeight / 2);
    personView.setImage(personData.gender, personData.avatar);
    personView.setTitle(personData.name);
}

經(jīng)過測量過程的坐標(biāo)計算之后默穴,onLayout就簡單很多了怔檩,根據(jù)算好的坐標(biāo),將對應(yīng)的成員view布局到viewgroup中蓄诽。

  1. 最后還有繪制連接線的過程薛训,有了坐標(biāo),這塊就是根據(jù)canvas的api畫線就行仑氛,不多做解釋了
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (ponits != null) {
        for (int i = 0; i < ponits.size(); i++) {
            Ponit ponit = ponits.get(i);
            if (ponit.connecPonit != null) {
                ponit.drawConnection(itemWidth, itemHeight, lineSpace, colSpace, radius);
                canvas.drawPath(ponit.getSegment(mPercent), mPaint);
            }
        }
    }
}

至此许蓖,家族樹就布局完成了,最后看一下最終效果


SVID_familytree.gif
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末调衰,一起剝皮案震驚了整個濱河市膊爪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嚎莉,老刑警劉巖米酬,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異趋箩,居然都是意外死亡赃额,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門叫确,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跳芳,“玉大人,你說我怎么就攤上這事竹勉》膳瑁” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吓歇。 經(jīng)常有香客問我孽水,道長,這世上最難降的妖魔是什么城看? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任女气,我火速辦了婚禮,結(jié)果婚禮上测柠,老公的妹妹穿的比我還像新娘炼鞠。我一直安慰自己,他們只是感情好轰胁,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布簇搅。 她就那樣靜靜地躺著,像睡著了一般软吐。 火紅的嫁衣襯著肌膚如雪瘩将。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天凹耙,我揣著相機與錄音姿现,去河邊找鬼。 笑死肖抱,一個胖子當(dāng)著我的面吹牛备典,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播意述,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼提佣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了荤崇?” 一聲冷哼從身側(cè)響起拌屏,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎术荤,沒想到半個月后倚喂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡瓣戚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年端圈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片子库。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡舱权,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仑嗅,到底是詐尸還是另有隱情宴倍,我是刑警寧澤张症,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站啊楚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏浑彰。R本人自食惡果不足惜恭理,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望郭变。 院中可真熱鬧颜价,春花似錦、人聲如沸诉濒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽未荒。三九已至专挪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間片排,已是汗流浹背寨腔。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留率寡,地道東北人迫卢。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像冶共,于是被迫代替她去往敵國和親乾蛤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容