ホームページ Java &#&チュートリアル Android カスタム ViewGroup は矢印付きの角丸長方形メニューを実装します

Android カスタム ViewGroup は矢印付きの角丸長方形メニューを実装します

Jan 16, 2017 pm 05:02 PM

この記事では、矢印が付いた角の丸い長方形のメニューを作成しましょう。おそらく次のようになります:

Android カスタム ViewGroup は矢印付きの角丸長方形メニューを実装します

では、上部の矢印がメニューのアンカーポイントに揃えられている必要があり、メニュー項目は反転色を押し、メニューは背景色とプレス色を設定可能。
最も簡単な方法は、UX に三角形の画像を上向きに投稿させることですが、これでは低すぎるのではないかと考えました。また、異なる解像度に適応するのは簡単ではないため、ViewGroup をカスタマイズすることも考えます。
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>
ログイン後にコピー

次に、ViewGroup を継承するクラスを作成し、コンストラクターでこれらの属性を初期化します
ここでは、obtainStyledAttributes() メソッドを使用して 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 の幅と高さを測定するために使用されます。

まず高さを考えてみましょう:
• まず、矢印と丸い角の高さを予約し、これら 2 つの項目を maxHeight に追加します
• 次に、ViewGroup には既製の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);
}
ログイン後にコピー

とても簡単だと思いませんか?もちろん、小さな疑問が 2 つあります:

1. 丸い角のために高さが確保されているのに、上下に 2 つの半径ではなく、1 つの半径だけが残っているのはなぜですか?
実はこれは表示効果の観点から考えたもので、上下に半径を残すとメニューの枠線が非常に太くなり、後でonLayout()を実装した際にわかります。メニュー項目をレイアウトすると、半径の半分が上に移動するため、境界線がより良く見えます。
2. Child のレイアウト パラメーターが強制的に MarginLayoutParams に変換されるのはなぜですか?
実際には、必要なレイアウト パラメーターのタイプを返すために、ここで別のメソッド GenerateLayoutParams() を書き直す必要があります。一般的には MarginLayoutParams が使用されますが、もちろん他の型やカスタム型を使用することもできます。

@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
  return new MarginLayoutParams(getContext(), attrs);
}
ログイン後にコピー

4. onLayout() メソッドを書き換えます

onLayout() メソッドは、名前が示すように、この ViewGroup 内のすべてのサブビューをレイアウトするために使用されます。
実際、各ビューにはlayout()メソッドがあります。必要なのは、適切な左/上/右/下の座標をこのメソッドに渡すことだけです。
ここで、メニュー項目をレイアウトするときに半径の半分を上に上げたので、topOffset は半径の半分だけを追加し、右側の座標も半径の半分だけを減少させることがわかります。

@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.dispatchDraw()メソッドを書き換えます

ここでは、ViewGroupコンテナを作成したので描画する必要がないため、そのdispatchDraw()メソッドを書き直す必要があります。特定のビューをオーバーライドする場合は、その onDraw() メソッドをオーバーライドすることもできます。
描画プロセスは3つのステップに分かれています:
1.角丸長方形を描画します
このステップは比較的単純で、CanvasのdrawRoundRect()を呼び出すだけで完了します。
2. 三角形の矢印を描画します
これには、構成されたプロパティに従ってパスを設定し、描画を完了するために Canvas のdrawPath() を呼び出す必要があります。
3. メニューの影を描く
簡単に言うと、色を変えて角丸長方形を描き、位置を少しずらして、もちろんぼかし効果も付けたものです。
ぼかし効果を取得するには、ペイントの setMaskFilter() を通じて設定する必要があり、レイヤーのハードウェア アクセラレーションをオフにする必要があります。これは API に明記されています。
さらに、ソース画像とターゲット画像のオーバーレイ モードを設定する必要があります。下の図からわかるように、影をメニューの後ろに重ねる必要があります。DST_OVER モードを選択する必要があります。

その他の詳細は、コードを見ると明らかになります:

@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 カスタム ViewGroup は矢印付きの角丸長方形メニューを実装します相关文章请关注PHP中文网!


このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Javaのクラスロードメカニズムは、さまざまなクラスローダーやその委任モデルを含むどのように機能しますか? Javaのクラスロードメカニズムは、さまざまなクラスローダーやその委任モデルを含むどのように機能しますか? Mar 17, 2025 pm 05:35 PM

Javaのクラスロードには、ブートストラップ、拡張機能、およびアプリケーションクラスローダーを備えた階層システムを使用して、クラスの読み込み、リンク、および初期化が含まれます。親の委任モデルは、コアクラスが最初にロードされ、カスタムクラスのLOAに影響を与えることを保証します

カフェインやグアバキャッシュなどのライブラリを使用して、Javaアプリケーションにマルチレベルキャッシュを実装するにはどうすればよいですか? カフェインやグアバキャッシュなどのライブラリを使用して、Javaアプリケーションにマルチレベルキャッシュを実装するにはどうすればよいですか? Mar 17, 2025 pm 05:44 PM

この記事では、カフェインとグアバキャッシュを使用してJavaでマルチレベルキャッシュを実装してアプリケーションのパフォーマンスを向上させています。セットアップ、統合、パフォーマンスの利点をカバーし、構成と立ち退きポリシー管理Best Pra

Javaで機能的なプログラミング技術を実装するにはどうすればよいですか? Javaで機能的なプログラミング技術を実装するにはどうすればよいですか? Mar 11, 2025 pm 05:51 PM

この記事では、Lambda式、Streams API、メソッド参照、およびオプションを使用して、機能プログラミングをJavaに統合することを調べます。 それは、簡潔さと不変性を通じてコードの読みやすさと保守性の改善などの利点を強調しています

キャッシュや怠zyなロードなどの高度な機能を備えたオブジェクトリレーショナルマッピングにJPA(Java Persistence API)を使用するにはどうすればよいですか? キャッシュや怠zyなロードなどの高度な機能を備えたオブジェクトリレーショナルマッピングにJPA(Java Persistence API)を使用するにはどうすればよいですか? Mar 17, 2025 pm 05:43 PM

この記事では、キャッシュや怠zyなロードなどの高度な機能を備えたオブジェクトリレーショナルマッピングにJPAを使用することについて説明します。潜在的な落とし穴を強調しながら、パフォーマンスを最適化するためのセットアップ、エンティティマッピング、およびベストプラクティスをカバーしています。[159文字]

高度なJavaプロジェクト管理、自動化の構築、依存関係の解像度にMavenまたはGradleを使用するにはどうすればよいですか? 高度なJavaプロジェクト管理、自動化の構築、依存関係の解像度にMavenまたはGradleを使用するにはどうすればよいですか? Mar 17, 2025 pm 05:46 PM

この記事では、Javaプロジェクト管理、自動化の構築、依存関係の解像度にMavenとGradleを使用して、アプローチと最適化戦略を比較して説明します。

非ブロッキングI/OにJavaのNIO(新しい入出力)APIを使用するにはどうすればよいですか? 非ブロッキングI/OにJavaのNIO(新しい入出力)APIを使用するにはどうすればよいですか? Mar 11, 2025 pm 05:51 PM

この記事では、単一のスレッドで複数の接続を効率的に処理するためにセレクターとチャネルを使用して、非ブロッキングI/O用のJavaのNIO APIについて説明します。 プロセス、利点(スケーラビリティ、パフォーマンス)、および潜在的な落とし穴(複雑さ、

適切なバージョン化と依存関係管理を備えたカスタムJavaライブラリ(JARファイル)を作成および使用するにはどうすればよいですか? 適切なバージョン化と依存関係管理を備えたカスタムJavaライブラリ(JARファイル)を作成および使用するにはどうすればよいですか? Mar 17, 2025 pm 05:45 PM

この記事では、MavenやGradleなどのツールを使用して、適切なバージョン化と依存関係管理を使用して、カスタムJavaライブラリ(JARファイル)の作成と使用について説明します。

ネットワーク通信にJavaのソケットAPIを使用するにはどうすればよいですか? ネットワーク通信にJavaのソケットAPIを使用するにはどうすればよいですか? Mar 11, 2025 pm 05:53 PM

この記事では、ネットワーク通信のためのJavaのソケットAPI、クライアントサーバーのセットアップ、データ処理、リソース管理、エラー処理、セキュリティなどの重要な考慮事項をカバーしています。 また、パフォーマンスの最適化手法も調査します

See all articles