Maison > Java > javaDidacticiel > ViewGroup personnalisé Android implémente un menu rectangulaire arrondi avec des flèches

ViewGroup personnalisé Android implémente un menu rectangulaire arrondi avec des flèches

高洛峰
Libérer: 2017-01-16 17:02:11
original
1750 Les gens l'ont consulté

Dans cet article, nous allons créer un menu rectangulaire arrondi avec des flèches, qui ressemble probablement à ceci :

ViewGroup personnalisé Android implémente un menu rectangulaire arrondi avec des flèches

Il est obligatoire que la flèche du haut soit alignée avec le point d'ancrage du menu. La couleur inverse de l'élément de menu, la couleur d'arrière-plan du menu et la couleur de la presse sont configurables.
Le plus simple est de demander à UX de poster une image triangulaire vers le haut, mais ensuite je me suis demandé si c'était trop bas, et ce n'est pas facile de s'adapter aux différentes résolutions, autant personnaliser un ViewGroup !
La personnalisation de ViewGroup est en fait très simple et suit essentiellement une certaine routine.

1. Définissez un attrs.xml
pour déclarer les attributs configurables de votre vue personnalisée, qui peuvent être librement configurés lors d'une utilisation future. Sept propriétés sont déclarées ici, à savoir : la largeur de la flèche, la hauteur de la flèche, le décalage horizontal de la flèche, le rayon du congé, la couleur d'arrière-plan du menu, la couleur de l'ombre et l'épaisseur de l'ombre.

<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>
Copier après la connexion

2. Écrivez une classe qui hérite de ViewGroup et initialisez ces attributs dans le constructeur
Ici, vous devez utiliser une méthode getStyledAttributes() pour obtenir un objet TypedArray, et alors vous pouvez obtenir la valeur d'attribut correspondante en fonction du type. Il convient de noter qu’une fois l’objet épuisé, il doit être libéré en appelant explicitement la méthode 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();
 }
Copier après la connexion

3. Réécrire la méthode onMeasure()

La méthode onMeasure(), comme son nom l'indique, est utilisée pour mesurez-vous Les dimensions de largeur et de hauteur de ce ViewGroup.

Considérons d'abord la hauteur :
•Tout d'abord, réservez la hauteur pour les flèches et les coins arrondis, et ajoutez ces deux éléments à maxHeight
•Mesurez ensuite tous les enfants visibles, que ViewGroup a fournis. -made MeasureChild() méthode
•Ajoutez ensuite la taille obtenue de l'enfant à maxHeight, et bien sûr considérez la configuration des marges supérieure et inférieure
•De plus, vous devez également prendre en compte le rembourrage supérieur et inférieur, comme ainsi que l'ombre La hauteur
•Réglez-la enfin via setMeasuredDimension() pour prendre effet

Considérez la largeur :
•Mesurez d'abord également tous les enfants visibles via la méthode MeasureChild()
• Comparez ensuite la largeur de ces enfants et la configuration des marges gauche et droite, sélectionnez la valeur maximale
• Ensuite, ajoutez un remplissage gauche et droit et une largeur d'ombre
• Enfin, définissez-la via setMeasuredDimension() pour prendre effet

@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);
}
Copier après la connexion

Ça n'a pas l'air simple ? Bien sûr, il y a deux petites questions :
1 Lorsque la hauteur est réservée au congé, pourquoi ne reste-t-il qu'un rayon au lieu de deux rayons haut et bas ?
En fait, ceci est considéré du point de vue de l'effet d'affichage. Si vous laissez un rayon en haut et en bas, la bordure du menu sera très épaisse et disgracieuse Lorsque vous implémenterez onLayout() plus tard, vous le trouverez. que lorsque nous disposerons les éléments de menu, ils monteront de la moitié du rayon pour que la bordure soit bien meilleure.
2. Pourquoi les paramètres de mise en page de Child peuvent-ils être convertis de force en MarginLayoutParams ?
Ici, vous devez en fait réécrire une autre méthode generateLayoutParams() pour renvoyer le type de paramètres de mise en page souhaité. Généralement, MarginLayoutParams est utilisé, mais bien sûr, vous pouvez également utiliser d'autres types ou types personnalisés.

@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
  return new MarginLayoutParams(getContext(), attrs);
}
Copier après la connexion

4. Réécrire la méthode onLayout()
La méthode onLayout(), comme son nom l'indique, est utilisée pour mettre en page toutes les sous-vues de ce ViewGroup.
En fait, chaque View a une méthode layout(). Tout ce que nous devons faire est de transmettre les coordonnées gauche/haut/droite/bas appropriées dans cette méthode.
Comme vous pouvez le voir ici, nous avons augmenté la moitié du rayon lors de la disposition des éléments de menu, donc topOffset n'a ajouté que la moitié du rayon, et les coordonnées sur le côté droit n'ont également diminué que la moitié du rayon.

@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);
  }
}
Copier après la connexion

5. Réécrivez la méthode dispatchDraw()
Ici, parce que nous écrivons un conteneur ViewGroup, il n'a pas besoin d'être dessiné, nous devons donc le réécrire dispatchDraw. () méthode. Si vous remplacez une vue spécifique, vous pouvez également remplacer sa méthode onDraw().
Le processus de dessin est divisé en trois étapes :
1. Dessinez un rectangle arrondi
Cette étape est relativement simple, il suffit d'appeler drawRoundRect() de Canvas et elle est terminée.
2. Dessinez une flèche triangulaire
Cela nécessite de définir un chemin basé sur les attributs configurés, puis d'appeler drawPath() de Canvas pour terminer le dessin.
3. Dessiner l'ombre du menu
Pour parler franchement, il s'agit de changer la couleur et de dessiner un rectangle arrondi, avec un léger décalage dans la position, et bien sûr un effet de flou.
Pour obtenir l'effet de flou, vous devez le configurer via setMaskFilter() de Paint, et vous devez désactiver l'accélération matérielle du calque. Ceci est clairement indiqué dans l'API.
De plus, vous devez également définir le mode de superposition de l'image source et de l'image cible. L'ombre doit évidemment se chevaucher derrière le menu. Comme le montre l'image ci-dessous, nous devons sélectionner le mode DST_OVER. .

D'autres détails seront clairs en regardant le 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);
 }
Copier après la connexion

六、在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>
Copier après la connexion

   

七、在代码里引用该layout XML
 这个就跟引用正常的layout XML没有什么区别了,这里主要是在创建弹出菜单的时候指定了刚刚那个layout XML,具体看下示例代码就清楚了。 
至此,一个完整的自定义ViewGroup的流程就算走了一遍了,后面有时间可能还会写一些复杂一些的自定义组件,但是万变不离其宗,基本的原理跟步骤都是相同的。本文就是抛砖引玉,希望能给需要自定义ViewGroup的朋友一些帮助。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持PHP中文网

更多ViewGroup personnalisé Android implémente un menu rectangulaire arrondi avec des flèches相关文章请关注PHP中文网!


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal