이 기사에서는 화살표가 있는 둥근 직사각형 메뉴를 만들 것입니다.
위쪽 화살표는 메뉴와 정렬되어야 합니다. 메뉴 앵커 포인트. 메뉴 항목 누르기 색상 반전, 메뉴 배경색 및 누르기 색상을 구성할 수 있습니다.
가장 간단한 방법은 UX에 삼각형 그림을 위쪽으로 올려달라고 요청하는 것이지만, 이것이 너무 낮은 것은 아닌가 하는 생각도 들고, 다른 해상도에 적응하는 것도 쉽지 않을 것 같습니다.
ViewGroup 사용자 정의는 실제로 매우 간단하며 기본적으로 특정 루틴을 따릅니다.
1. attrs.xml
을 정의하여 나중에 사용할 때 자유롭게 구성할 수 있는 사용자 정의 보기의 구성 가능한 속성을 선언합니다. 여기에는 화살표 너비, 화살표 높이, 화살표 수평 오프셋, 필렛 반경, 메뉴 배경색, 그림자 색상 및 그림자 두께 등 7가지 속성이 선언됩니다.
<resources> <declare-styleable name="ArrowRectangleView"> <attr name="arrow_width" format="dimension" /> <attr name="arrow_height" format="dimension" /> <attr name="arrow_offset" format="dimension" /> <attr name="radius" format="dimension" /> <attr name="background_color" format="color" /> <attr name="shadow_color" format="color" /> <attr name="shadow_thickness" format="dimension" /> </declare-styleable> </resources>
2. ViewGroup을 상속하는 클래스를 작성하고 생성자에서 이러한 속성을 초기화합니다.
여기서 GetStyledAttributes() 메서드를 사용하여 TypedArray 객체를 가져와야 합니다. 유형에 따라 해당 속성 값을 가져올 수 있습니다. 객체를 다 사용한 후에는 recycle() 메서드를 명시적으로 호출하여 객체를 해제해야 한다는 점에 유의해야 합니다.
public class ArrowRectangleView extends ViewGroup { ... ... public ArrowRectangleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArrowRectangleView, defStyleAttr, 0); for (int i = 0; i < a.getIndexCount(); i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.ArrowRectangleView_arrow_width: mArrowWidth = a.getDimensionPixelSize(attr, mArrowWidth); break; case R.styleable.ArrowRectangleView_arrow_height: mArrowHeight = a.getDimensionPixelSize(attr, mArrowHeight); break; case R.styleable.ArrowRectangleView_radius: mRadius = a.getDimensionPixelSize(attr, mRadius); break; case R.styleable.ArrowRectangleView_background_color: mBackgroundColor = a.getColor(attr, mBackgroundColor); break; case R.styleable.ArrowRectangleView_arrow_offset: mArrowOffset = a.getDimensionPixelSize(attr, mArrowOffset); break; case R.styleable.ArrowRectangleView_shadow_color: mShadowColor = a.getColor(attr, mShadowColor); break; case R.styleable.ArrowRectangleView_shadow_thickness: mShadowThickness = a.getDimensionPixelSize(attr, mShadowThickness); break; } } a.recycle(); }
3. onMeasure() 메서드 다시 작성
onMeasure() 메서드는 이름에서 알 수 있듯이 ViewGroup 너비 및 높이 치수.
먼저 높이를 고려해 보겠습니다.
•먼저 화살표와 둥근 모서리의 높이를 예약하고 이 두 항목을 maxHeight에 추가합니다.
•그런 다음 ViewGroup이 제공한 모든 보이는 하위 항목을 측정합니다. -made MeasureChild() 메소드
•그런 다음 얻은 아이의 키를 maxHeight에 추가하고 물론 상단 및 하단 여백 구성을 고려합니다
•또한 다음과 같이 상단 및 하단 패딩도 고려해야 합니다. 그림자 높이
• 마지막으로 setMeasuredDimension()을 통해 설정하여 적용합니다.
너비를 고려하세요.
•먼저 MeasureChild() 메서드를 통해 보이는 모든 하위 항목도 측정합니다
• 그런 다음 이 하위 항목의 너비와 왼쪽 및 오른쪽 여백 구성을 비교하고 최대값을 선택합니다.
• 다음으로 왼쪽 및 오른쪽 패딩과 그림자 너비를 추가합니다.
• 마지막으로 setMeasuredDimension()을 통해 설정하여 적용합니다
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); int maxWidth = 0; // reserve space for the arrow and round corners int maxHeight = mArrowHeight + mRadius; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); if (child.getVisibility() != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = maxHeight + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; } } maxWidth = maxWidth + getPaddingLeft() + getPaddingRight() + mShadowThickness; maxHeight = maxHeight + getPaddingTop() + getPaddingBottom() + mShadowThickness; setMeasuredDimension(maxWidth, maxHeight); }
심플해 보이지 않나요? 물론, 두 가지 작은 질문이 있습니다.
1. 높이가 둥근 모서리에 예약되어 있는데 왜 위아래 반경이 두 개 있는 대신 반경이 하나만 남나요?
사실 이는 디스플레이 효과 측면에서 고려한 것입니다. 상단과 하단에 반경을 남겨두면 메뉴의 테두리가 매우 두껍고 보기 흉해집니다. 메뉴 항목을 배치할 때 테두리가 훨씬 더 보기 좋게 보이도록 반경의 절반을 이동합니다.
2. 왜 Child의 레이아웃 매개변수를 MarginLayoutParams로 강제 변환할 수 있나요?
여기서 실제로 원하는 레이아웃 매개변수 유형을 반환하려면 generateLayoutParams() 메서드를 다시 작성해야 합니다. 일반적으로 MarginLayoutParams를 사용하지만, 물론 다른 유형이나 사용자 정의 유형을 사용할 수도 있습니다.
@Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); }
4. onLayout() 메서드 다시 작성
onLayout() 메서드는 이름에서 알 수 있듯이 이 ViewGroup의 모든 하위 뷰를 레이아웃하는 데 사용됩니다.
사실 각 뷰에는 레이아웃() 메서드가 있습니다. 우리가 해야 할 일은 적절한 왼쪽/상단/오른쪽/하단 좌표를 이 메서드에 전달하는 것뿐입니다.
여기서 볼 수 있듯이 메뉴 항목을 배치할 때 반경을 절반만 늘렸기 때문에 topOffset은 반경을 절반만 추가했고, 오른쪽 좌표도 반경을 절반만 줄였습니다. <… 방법. 특정 뷰를 재정의하는 경우 해당 뷰의 onDraw() 메서드도 재정의할 수 있습니다.
1. 둥근 사각형을 그립니다
이 단계는 비교적 간단합니다. Canvas의 drawRoundRect()를 호출하면 완료됩니다.2. 삼각형 화살표를 그립니다.
구성된 속성에 따라 경로를 설정한 다음 Canvas의 drawPath()를 호출하여 그리기를 완료해야 합니다.
3. 메뉴 그림자 그리기
직접 말하면 색상을 변경하고 위치에 약간의 오프셋을 가한 둥근 사각형을 그리는 것입니다. 물론 흐림 효과도 있습니다.
블러 효과를 얻으려면 Paint의 setMaskFilter()를 통해 구성해야 하며 레이어의 하드웨어 가속을 꺼야 합니다. 이는 API에 명확하게 명시되어 있습니다.
또한 소스 이미지와 대상 이미지의 오버레이 모드도 설정해야 합니다. 아래 그림에서 볼 수 있듯이 그림자는 당연히 DST_OVER 모드를 선택해야 합니다. .
다른 세부사항은 코드를 보면 알 수 있습니다:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); int topOffset = t + mArrowHeight + mRadius/2; int top = 0; int bottom = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); top = topOffset + i * child.getMeasuredHeight(); bottom = top + child.getMeasuredHeight(); child.layout(l, top, r - mRadius/2 - mShadowThickness, bottom); } }
六、在layout XML中引用该自定义ViewGroup
到此为止,自定义ViewGroup的实现已经完成了,那我们就在项目里用一用吧!使用自定义ViewGroup和使用系统ViewGroup组件有两个小区别:
一、是要指定完整的包名,否则运行的时候会报找不到该组件。
二、是配置自定义属性的时候要需要另外指定一个名字空间,避免跟默认的android名字空间混淆。比如这里就指定了一个新的app名字空间来引用自定义属性。
<?xml version="1.0" encoding="utf-8"?> <com.xinxin.arrowrectanglemenu.widget.ArrowRectangleView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:background="@android:color/transparent" android:paddingLeft="3dp" android:paddingRight="3dp" android:splitMotionEvents="false" app:arrow_offset="31dp" app:arrow_width="16dp" app:arrow_height="8dp" app:radius="5dp" app:background_color="#ffb1df83" app:shadow_color="#66000000" app:shadow_thickness="5dp"> <LinearLayout android:id="@+id/cmx_toolbar_menu_turn_off" android:layout_width="wrap_content" android:layout_height="42dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textSize="16sp" android:textColor="#FF393F4A" android:paddingLeft="16dp" android:paddingRight="32dp" android:clickable="false" android:text="Menu Item #1"/> </LinearLayout> <LinearLayout android:id="@+id/cmx_toolbar_menu_feedback" android:layout_width="wrap_content" android:layout_height="42dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textSize="16sp" android:textColor="#FF393F4A" android:paddingLeft="16dp" android:paddingRight="32dp" android:clickable="false" android:text="Menu Item #2"/> </LinearLayout> </com.xinxin.arrowrectanglemenu.widget.ArrowRectangleView>
七、在代码里引用该layout XML
这个就跟引用正常的layout XML没有什么区别了,这里主要是在创建弹出菜单的时候指定了刚刚那个layout XML,具体看下示例代码就清楚了。
至此,一个完整的自定义ViewGroup的流程就算走了一遍了,后面有时间可能还会写一些复杂一些的自定义组件,但是万变不离其宗,基本的原理跟步骤都是相同的。本文就是抛砖引玉,希望能给需要自定义ViewGroup的朋友一些帮助。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持PHP中文网
更多Android 맞춤 ViewGroup은 화살표가 있는 둥근 직사각형 메뉴를 구현합니다.相关文章请关注PHP中文网!