In this article, we will make a rounded rectangular menu with arrows, which probably looks like this:
Requires that the top arrow should be aligned with the menu anchor point. Menu item press inverse color, menu background color and press color are configurable.
The simplest way is to ask UX to post a triangular picture upwards, but then I thought about whether this is too low, and it is not easy to adapt to different resolutions, so I might as well customize a ViewGroup!
Customizing ViewGroup is actually very simple, and basically follows a certain routine.
1. Define an attrs.xml
is to declare the configurable attributes of your custom View, which can be freely configured when used in the future. Seven attributes are declared here, namely: arrow width, arrow height, arrow horizontal offset, fillet radius, menu background color, shadow color, and shadow thickness.
<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. Write a class that inherits ViewGroup and initialize these attributes in the constructor
Here you need to use an obtainStyledAttributes() method to obtain a TypedArray object, and then you can Get the corresponding attribute value according to the type. It should be noted that after the object is used up, it needs to be released by explicitly calling the recycle() method.
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. Rewrite the onMeasure() method
onMeasure() method, as the name suggests, is used to measure your ViewGroup width and height dimensions.
Let’s consider the height first:
•First, reserve the height for arrows and rounded corners, and add these two items to maxHeight
•Then measure all visible children, which ViewGroup has provided Ready-made measureChild() method
•Next, add the obtained height of the child to maxHeight, and of course consider the upper and lower margin configuration
•In addition, you also need to consider the upper and lower padding, as well as the shadow The height
•Finally set it through setMeasuredDimension() to take effect
Consider the width:
•First also measure all visible children through the measureChild() method
•Then compare these children Width and left and right margin configuration, select the maximum value
•Next, add left and right padding, and shadow width
•Finally set it through setMeasuredDimension() to take effect
@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); }
Doesn’t it look simple? Of course, there are two small questions:
1. When the height is reserved for the rounded corners, why is only one radius left instead of two radii up and down?
In fact, this is considered from the perspective of display effect. If you leave a radius at the top and bottom, the border of the menu will be very thick and unsightly. When you implement onLayout() later, you will find that when we layout the menu items, they will go up. Move half the radius so the border looks much better.
2. Why can Child's layout parameters be forcibly converted to MarginLayoutParams?
Here you actually need to override another method generateLayoutParams() to return the type of layout parameters you want. Generally, MarginLayoutParams is used, but of course you can also use other types or custom types.
@Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); }
4. Rewrite the onLayout() method
The onLayout() method, as the name suggests, is used to layout all sub-Views in this ViewGroup.
In fact, each View has a layout() method. All we need to do is to pass the appropriate left/top/right/bottom coordinates into this method.
You can see here that when we laid out the menu items, we raised half the radius, so topOffset only added half the radius, and the coordinates on the right side also only reduced half the radius.
@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); } }
5. Rewrite the dispatchDraw() method
Here because we have written a ViewGroup container, it does not need to be drawn, so we need to rewrite its dispatchDraw ()method. If you are overriding a specific View, you can also override its onDraw() method.
The drawing process is divided into three steps:
1. Draw a rounded rectangle
This step is relatively simple, just call drawRoundRect() of Canvas and it is completed.
2. Draw a triangular arrow
This requires setting a path according to the configured properties, and then calling Canvas's drawPath() to complete the drawing.
3. Draw menu shadow
To put it bluntly, this is to change the color and draw a rounded rectangle, with a slightly offset position, and of course a blur effect.
To obtain the blur effect, you need to configure it through Paint's setMaskFilter(), and you need to turn off the hardware acceleration of the layer. This is clearly stated in the API.
In addition, you also need to set the overlay mode of the source image and the target image. The shadow obviously needs to be overlapped behind the menu. As shown in the figure below, we need to select the DST_OVER mode.
Other details will be clear by looking at the code:
@Override protected void dispatchDraw(Canvas canvas) { // disable h/w acceleration for blur mask filter setLayerType(View.LAYER_TYPE_SOFTWARE, null); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(mBackgroundColor); paint.setStyle(Paint.Style.FILL); // set Xfermode for source and shadow overlap paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); // draw round corner rectangle paint.setColor(mBackgroundColor); canvas.drawRoundRect(new RectF(0, mArrowHeight, getMeasuredWidth() - mShadowThickness, getMeasuredHeight() - mShadowThickness), mRadius, mRadius, paint); // draw arrow Path path = new Path(); int startPoint = getMeasuredWidth() - mArrowOffset; path.moveTo(startPoint, mArrowHeight); path.lineTo(startPoint + mArrowWidth, mArrowHeight); path.lineTo(startPoint + mArrowWidth / 2, 0); path.close(); canvas.drawPath(path, paint); // draw shadow if (mShadowThickness > 0) { paint.setMaskFilter(new BlurMaskFilter(mShadowThickness, BlurMaskFilter.Blur.OUTER)); paint.setColor(mShadowColor); canvas.drawRoundRect(new RectF(mShadowThickness, mArrowHeight + mShadowThickness, getMeasuredWidth() - mShadowThickness, getMeasuredHeight() - mShadowThickness), mRadius, mRadius, paint); } super.dispatchDraw(canvas); }
六、在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 custom ViewGroup implements rounded rectangular menu with arrows相关文章请关注PHP中文网!

The class loader ensures the consistency and compatibility of Java programs on different platforms through unified class file format, dynamic loading, parent delegation model and platform-independent bytecode, and achieves platform independence.

The code generated by the Java compiler is platform-independent, but the code that is ultimately executed is platform-specific. 1. Java source code is compiled into platform-independent bytecode. 2. The JVM converts bytecode into machine code for a specific platform, ensuring cross-platform operation but performance may be different.

Multithreading is important in modern programming because it can improve program responsiveness and resource utilization and handle complex concurrent tasks. JVM ensures the consistency and efficiency of multithreads on different operating systems through thread mapping, scheduling mechanism and synchronization lock mechanism.

Java's platform independence means that the code written can run on any platform with JVM installed without modification. 1) Java source code is compiled into bytecode, 2) Bytecode is interpreted and executed by the JVM, 3) The JVM provides memory management and garbage collection functions to ensure that the program runs on different operating systems.

Javaapplicationscanindeedencounterplatform-specificissuesdespitetheJVM'sabstraction.Reasonsinclude:1)Nativecodeandlibraries,2)Operatingsystemdifferences,3)JVMimplementationvariations,and4)Hardwaredependencies.Tomitigatethese,developersshould:1)Conduc

Cloud computing significantly improves Java's platform independence. 1) Java code is compiled into bytecode and executed by the JVM on different operating systems to ensure cross-platform operation. 2) Use Docker and Kubernetes to deploy Java applications to improve portability and scalability.

Java'splatformindependenceallowsdeveloperstowritecodeonceandrunitonanydeviceorOSwithaJVM.Thisisachievedthroughcompilingtobytecode,whichtheJVMinterpretsorcompilesatruntime.ThisfeaturehassignificantlyboostedJava'sadoptionduetocross-platformdeployment,s

Containerization technologies such as Docker enhance rather than replace Java's platform independence. 1) Ensure consistency across environments, 2) Manage dependencies, including specific JVM versions, 3) Simplify the deployment process to make Java applications more adaptable and manageable.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Atom editor mac version download
The most popular open source editor

Dreamweaver Mac version
Visual web development tools

PhpStorm Mac version
The latest (2018.2.1) professional PHP integrated development tool

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

EditPlus Chinese cracked version
Small size, syntax highlighting, does not support code prompt function