文章

头像框控件

头像框控件

头像框控件

  1. 自定义组合控件,头像框和头像成一定比例,但有个问题,就是没有头像框的时候,会多出来一点间距,这个调整 ui 工作量就大了
  2. 头像框控件固定,间距也固定,头像框就盖在头像上面,通过 clipChildren 来控制绘制到父控件中

Mashi 头像框控件

  1. 支持 webp 静态图
  2. 支持 svga 动态图
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// 头像:头像框 150:198
open class AvatarBoxView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private val ivAvatar: BaseImageView by lazy { findViewById<BaseImageView>(R.id.iv_avatar) }
    private val ivAvatarBoxStatic: BaseImageView by lazy { findViewById<BaseImageView>(R.id.iv_avatar_box_static) }
    private val ivAvatarBoxSVGA: CommonSVGAView by lazy { findViewById<CommonSVGAView>(R.id.iv_avatar_box_svga) }
    private var goodsDetail: StoreGoodsDetail? = null
    private var tag = "default"
    private var clipCount = 1

    companion object {
        private const val TAG = StoreConstants.TAG
    }

    init {
        LayoutInflater.from(context).inflate(R.layout.view_avatar_layout, this, true)
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.AvatarBoxView)
        try {
            clipCount = typedArray?.getInt(R.styleable.AvatarBoxView_clip_count, 1) ?: 1
        } finally {
            typedArray?.recycle()
        }
    }

    fun getAvatarBoxSVGA(): SVGAImageView? {
        if (goodsDetail == null || goodsDetail?.isStaticPreview() != false) {
            return null
        }
        return ivAvatarBoxSVGA
    }

    fun isAvatarBoxSVGAShow(): Boolean {
        val avatarBoxSVGA = getAvatarBoxSVGA()
        return avatarBoxSVGA != null && avatarBoxSVGA.drawable != null
    }

    fun showAvatarBoxSVGADirect() {
        getAvatarBoxSVGA()?.startAnimation()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        // LogUtils.d(TAG, "onMeasure widthSize=$widthSize, heightSize=$heightSize")
        if (widthSize == 0 && heightSize == 0) {
            // If there are no constraints on size, let AvatarView measure
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)

            // Now use the smallest of the measured dimensions for both dimensions
            val minSize = measuredWidth.coerceAtMost(measuredHeight)
            setMeasuredDimension(minSize, minSize)
            // LogUtils.w(TAG, "onMeasure minSize=$minSize")
            return
        }

        val size: Int
        size = if (widthSize == 0 || heightSize == 0) {
            // If one of the dimensions has no restriction on size, set both dimensions to be the
            // on that does
            widthSize.coerceAtLeast(heightSize)
            // LogUtils.w(TAG, "onMeasure(widthSize == 0 || heightSize == 0) size=$size")
        } else {
            // Both dimensions have restrictions on size, set both dimensions to be the
            // smallest of the two
            widthSize.coerceAtMost(heightSize)
            // LogUtils.w(TAG, "onMeasure(widthSize and heightSize != 0) size=$size")
        }

        val newMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
        super.onMeasure(newMeasureSpec, newMeasureSpec)
    }


    override fun measureChildWithMargins(child: View?, parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int, heightUsed: Int) {
        val widthSize = MeasureSpec.getSize(parentWidthMeasureSpec)
        val heightSize = MeasureSpec.getSize(parentHeightMeasureSpec)
        if (child?.id == R.id.iv_avatar_box_static || child?.id == R.id.iv_avatar_box_svga) {
            // LogUtils.d(TAG, "measureChildWithMargins(R.id.iv_avatar) widthSize=$widthSize, heightSize=$heightSize child=$child")
            val size: Int
            if (widthSize == 0 || heightSize == 0) {
                // If one of the dimensions has no restriction on size, set both dimensions to be the
                // on that does
                size = widthSize.coerceAtLeast(heightSize)
                // LogUtils.w(TAG, "measureChildWithMargins(widthSize == 0 || heightSize == 0) size=$size")
            } else {
                // Both dimensions have restrictions on size, set both dimensions to be the
                // smallest of the two
                size = widthSize.coerceAtMost(heightSize)
                //LogUtils.w(TAG, "measureChildWithMargins(widthSize and heightSize != 0) size=$size")
            }
            val avatarSize = size * (198.0 / 150.0) // FIXME:ZFS 2020年03月03日20:07:53 改成可配置?
            // LogUtils.w(TAG, "measureChildWithMargins final size avatarSize=$avatarSize(${avatarSize.toInt()})")
            val newMeasureSpec = MeasureSpec.makeMeasureSpec(avatarSize.toInt(), MeasureSpec.EXACTLY)
            super.measureChildWithMargins(child, newMeasureSpec, widthUsed, newMeasureSpec, heightUsed)
        } else {
            // LogUtils.d(TAG, "measureChildWithMargins widthSize=$widthSize, heightSize=$heightSize,widthUsed=$widthUsed, heightUsed=$heightUsed, child=$child")
            super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed)
        }
    }

    fun tag(tag: String): AvatarBoxView {
        this.tag = tag
        return this
    }

    fun showAvatarAndBox(user: User?): AvatarBoxView {
        showAvatar(user).showAvatarBox(user?.avatarBox)
        return this
    }

    fun showAvatar(user: User?): AvatarBoxView {
        ivAvatar.visible().showAvatar(user?.avatar ?: "")
        return this
    }

    fun showAvatarBox(storeGoodsDetail: StoreGoodsDetail?): AvatarBoxView {
        this.goodsDetail = storeGoodsDetail
        if (storeGoodsDetail?.previewPic == null) {
            hideAvatarBox()
        } else {
            storeGoodsDetail.previewPic?.apply {
                if (isStatic) {
                    // LogUtils.d(TAG, "${anchor("showAvatarBox")}静态头像框,picUrl=$picUrl")
//                ivAvatarBoxStatic.visible().showAvatar(picUrl ?: "")
                    ivAvatarBoxStatic.setImageURI("")
                    ivAvatarBoxSVGA.gone()
                    ImageLoaderManager.createImageOptions(ivAvatarBoxStatic.visible(), getPreviewPicUrl(storeGoodsDetail)
                            ?: "")
                            .setOnLoaderResultCallBack(object : OnLoaderResultCallBack {
                                override fun onSucc(animatable: Animatable?) {
                                }

                                override fun onFail(throwable: Throwable?) {
                                    LogUtils.printStackTrace(throwable)
                                    LogUtils.w(TAG, "${this@AvatarBoxView.anchor("showAvatarBox")}[$tag]静态头像框加载失败(${throwable?.message}),picUrl=$picUrl", true)
                                }
                            })
                            .show()
                } else {
                    val pic = getPreviewPicUrl(storeGoodsDetail) ?: ""
                    ivAvatarBoxStatic.setImageURI("")
                    ivAvatarBoxSVGA.stop()
                    ivAvatarBoxSVGA.tag = pic
                    ivAvatarBoxSVGA.visible()
                            .loop()
                            .setCallback(object : CommonSVGAView.ParseCallback {
                                override fun onError() {
                                    LogUtils.w(TAG, "${this@AvatarBoxView.anchor("showAvatarBox")}[$tag]动态SVGA头像框加载失败,picUrl=$picUrl", true)
                                }

                            })
                            .show(pic)
                }
            }
        }
        return this
    }

    // 隐藏头像框
    fun hideAvatarBox() {
        LogUtils.w(TAG, "${anchor("hideAvatarBox")}[$tag]隐藏头像框静态/SVGA,previewPic=${goodsDetail?.previewPic}", true)
        ivAvatarBoxStatic.gone()
        ivAvatarBoxSVGA.gone()
    }

    override fun dispatchDraw(canvas: Canvas?) {
        clip(this)
        super.dispatchDraw(canvas)
    }

    private fun clip(v: ViewGroup?) {
        if (v == null || clipCount < 0) {
            return
        }
        v.clipChildrenNot().clipPaddingNot()
//        LogUtils.i(TAG, "${anchor("dispatchDraw")}[$tag] clipCount=$clipCount, parent_id=${v.id},parent clipChildren & clipToPadding = false,parent=$v")
        while (clipCount >= 0) {
            clipCount--
            clip(v.parent as? ViewGroup)
        }
    }

}

糗百静态图头像框

在 ImageView 的 onDraw,通过 canvas translate 到父控件外,再通过 clipChildren 来实现

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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
public class VHeadSimpleDrawee extends QBThemeImageView {
    /**
     * 大v图标的大小跟头像本身大小比例是 0.35 找iOS同学确认的,最好在初始化的时候写进去
     */
    public static final float SCALE = 0.35f;
    private Drawable mBottomDrawable = null;
    private int drawableWidth;
    private int drawableHeight;
    private int defValue = 0;
    private DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;

    public VHeadSimpleDrawee(Context context, GenericDraweeHierarchy hierarchy) {
        super(context, hierarchy);
        init(null);
    }


    public VHeadSimpleDrawee(Context context) {
        super(context);
        init(null);
    }

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

    public VHeadSimpleDrawee(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
    }

    private void init(AttributeSet attributeSet) {
        defValue = getContext().getResources().getDimensionPixelSize(R.dimen.qb_px_15);
        TypedArray a = getContext().obtainStyledAttributes(attributeSet, R.styleable.VHeadSimpleDrawee);
        if (a != null) {
            try {
                drawableHeight = a.getDimensionPixelSize(R.styleable.VHeadSimpleDrawee_drawable_height, defValue);
                drawableWidth = a.getDimensionPixelSize(R.styleable.VHeadSimpleDrawee_drawable_width, defValue);
            } finally {
                a.recycle();
            }
        }

        GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources()).build();
        mDraweeHolder = DraweeHolder.create(hierarchy, getContext());
        mDraweeHolder.getTopLevelDrawable().setCallback(this);
    }

    public void setDrawableSize(int width, int height) {
        drawableHeight = height;
        drawableWidth = width;
        invalidate();
    }

    public void showVImage(List<TalentBean> talentBeans) {
        showVImage((talentBeans != null && talentBeans.size() > 0) ? talentBeans.get(0) : null);
    }

    private void showVImage(TalentBean talent) {
        if (talent != null) {
            mBottomDrawable =  getResources().getDrawable(getResFromTalent(talent));
        } else {
            mBottomDrawable = null;
        }
        invalidate();
    }

    public static int getResFromTalent(TalentBean talent) {
        if (talent == null) {
            return 0;
        }
        if (talent.cmd < 0) {
            return UIHelper.isNightTheme() ? R.drawable.im_ic_certification_night : R.drawable.im_ic_certification;
        } else {
            return UIHelper.isNightTheme() ?R.drawable.icon_head_v_night : R.drawable.icon_head_v;
        }
    }

    public void photoFrame(String photoFrame) {
        DraweeController controller;
        Uri uri = Uri.EMPTY;
        try {
            if (!TextUtils.isEmpty(photoFrame)) {
                uri = Uri.parse(photoFrame);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (mDraweeHolder != null) {
            controller = Fresco.newDraweeControllerBuilder()
                    .setUri(uri)
                    .setOldController(mDraweeHolder.getController())
                    .build();
            mDraweeHolder.setController(controller);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        ViewGroup parent = (ViewGroup) getParent();
        if (parent != null) {
            parent.setClipChildren(false);
            parent.setClipToPadding(false);
        }

        if (getMeasuredWidth() > 0 && mDraweeHolder != null && mDraweeHolder.getTopLevelDrawable() != null) {
            int width = getMeasuredWidth();
            int photoFrameSize = (int) (width * 1.4f);
            canvas.save();
            canvas.translate(-(photoFrameSize - width)/2, -(photoFrameSize - width)/2);
            Drawable drawable = mDraweeHolder.getTopLevelDrawable();
            drawable.setBounds(new Rect(0, 0, photoFrameSize, photoFrameSize));
            drawable.draw(canvas);
            canvas.restore();
        }

        int imgWidth = getMeasuredWidth();
        int imgHeight = getMeasuredHeight();
        if (imgHeight > 0 && imgWidth > 0) {
            drawableHeight = (int) (imgHeight * (SCALE));
            drawableWidth = (int) (imgWidth * (SCALE));
        }
        if (mBottomDrawable != null) {
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();
            int left = width - drawableWidth;
            int top = height - drawableHeight;
            mBottomDrawable.setColorFilter(getColorFilter());
            mBottomDrawable.setBounds(
                    left,
                    top,
                    width,
                    height);
            mBottomDrawable.draw(canvas);
        }
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mDraweeHolder != null) {
            mDraweeHolder.onDetach();
        }
    }

    @Override
    public void onStartTemporaryDetach() {
        super.onStartTemporaryDetach();
        if (mDraweeHolder != null) {
            mDraweeHolder.onDetach();
        }

    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mDraweeHolder != null) {
            mDraweeHolder.onAttach();
        }

    }

    @Override
    public void onFinishTemporaryDetach() {
        super.onFinishTemporaryDetach();
        if (mDraweeHolder != null) {
            mDraweeHolder.onAttach();
        }

    }

    @Override
    protected boolean verifyDrawable(@NonNull Drawable dr) {
        if (mDraweeHolder != null && dr == mDraweeHolder.getTopLevelDrawable()) {
            return true;
        }
        return super.verifyDrawable(dr);
    }
}
本文由作者按照 CC BY 4.0 进行授权