Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Tạo Notification tùy chỉnh (Custom Layout, Text Color) trên Android

Giới thiệu

Bài viết này hướng dẫn cách tạo Notification tùy chỉnh (Custom Notification) trên Android sử dụng RemoteViews và NotificationCompat.Builder. Cho phép kiểm soát hoàn toàn layout, màu chữ, font, icon, hình ảnh trong notification.

Các bước thực hiện

Bước 1: Tạo dự án mới

Mở Android Studio, chọn File → New Project và điền thông tin cần thiết.

Bước 2: Layout chính (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnStandardNotification"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:text="Notification chuẩn"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <Button
        android:id="@+id/btnCustomNotification"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:text="Custom Notification (RemoteViews)"
        app:layout_constraintTop_toBottomOf="@id/btnStandardNotification"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Bước 3: Layout Custom Notification (res/layout/custom_notification.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:background="@android:color/white">

    <ImageView
        android:id="@+id/imagenotileft"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_alignParentStart="true"
        android:layout_centerVertical="true"
        android:contentDescription="App icon"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toEndOf="@id/imagenotileft"
        android:layout_marginStart="16dp"
        android:layout_alignTop="@id/imagenotileft"
        android:text="Custom Title"
        android:textSize="16sp"
        android:textStyle="bold"
        android:textColor="#212121" />

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toEndOf="@id/imagenotileft"
        android:layout_marginStart="16dp"
        android:layout_below="@id/title"
        android:layout_marginTop="4dp"
        android:text="Custom message content"
        android:textSize="14sp"
        android:textColor="#757575" />

    <ImageView
        android:id="@+id/imagenotiright"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_alignParentEnd="true"
        android:layout_centerVertical="true"
        android:padding="8dp"
        android:contentDescription="Action icon"
        android:scaleType="centerCrop" />

</RelativeLayout>

Bước 4: Layout Standard Notification (res/layout/notification_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/notification"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Notification - 1" />

    <Button
        android:id="@+id/customnotification"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_below="@id/notification"
        android:layout_marginTop="16dp"
        android:text="Custom Notification - 1" />

</RelativeLayout>

Bước 5: Notification View Activity (res/layout/notification_view.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <TextView
        android:id="@+id/lbltitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Title:"
        android:textStyle="bold"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toEndOf="@id/lbltitle"
        android:layout_marginStart="8dp"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/lbltext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/lbltitle"
        android:layout_marginTop="8dp"
        android:text="Message:"
        android:textStyle="bold"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/title"
        android:layout_toEndOf="@id/lbltext"
        android:layout_marginStart="8dp"
        android:textSize="16sp" />

</RelativeLayout>

Bước 6: Activity chính (src/MainActivity.java)

package com.app.sample;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.RemoteViews;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private static final String CHANNEL_ID = "custom_notification_channel";
    private static final int NOTIFICATION_ID_STANDARD = 100;
    private static final int NOTIFICATION_ID_CUSTOM = 101;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        createNotificationChannel();

        Button btnStandard = findViewById(R.id.btnStandardNotification);
        Button btnCustom = findViewById(R.id.btnCustomNotification);

        btnStandard.setOnClickListener(v -> showStandardNotification());
        btnCustom.setOnClickListener(v -> showCustomNotification());
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = "Custom Notifications";
            String description = "Kênh cho custom notification demo";
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
            channel.setDescription(description);
            channel.enableLights(true);
            channel.setLightColor(Color.BLUE);
            
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }

    private void showStandardNotification() {
        Intent intent = new Intent(this, NotificationViewActivity.class);
        intent.putExtra("title", "Standard Notification");
        intent.putExtra("text", "Đây là notification chuẩn hệ thống");
        
        PendingIntent pendingIntent = PendingIntent.getActivity(
            this, 0, intent, 
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("Standard Notification")
            .setContentText("Đây là notification chuẩn hệ thống")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
            .addAction(R.drawable.ic_launcher_foreground, "Action", pendingIntent);

        NotificationManagerCompat.from(this).notify(NOTIFICATION_ID_STANDARD, builder.build());
        Toast.makeText(this, "Đã gửi Standard Notification", Toast.LENGTH_SHORT).show();
    }

    private void showCustomNotification() {
        // Tạo RemoteViews từ custom layout
        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.custom_notification);
        
        // Set content cho RemoteViews
        remoteViews.setTextViewText(R.id.title, "Custom Notification Title");
        remoteViews.setTextViewText(R.id.text, "Nội dung tùy chỉnh với màu chữ, font, layout riêng");
        remoteViews.setTextColor(R.id.title, Color.parseColor("#1565C0")); // Blue đậm
        remoteViews.setTextColor(R.id.text, Color.parseColor("#424242"));  // Grey đậm
        
        remoteViews.setImageViewResource(R.id.imagenotileft, R.drawable.ic_launcher_foreground);
        remoteViews.setImageViewResource(R.id.imagenotiright, R.drawable.ic_launcher_foreground);

        Intent intent = new Intent(this, NotificationViewActivity.class);
        intent.putExtra("title", "Custom Notification");
        intent.putExtra("text", "Bạn đã click vào Custom Notification");
        
        PendingIntent pendingIntent = PendingIntent.getActivity(
            this, 0, intent, 
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );

        // PendingIntent cho nút action bên phải (nếu có)
        PendingIntent actionPendingIntent = PendingIntent.getBroadcast(
            this, 1, new Intent("CUSTOM_ACTION"), 
            PendingIntent.FLAG_IMMUTABLE
        );
        remoteViews.setOnClickPendingIntent(R.id.imagenotiright, actionPendingIntent);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_launcher_foreground) // Bắt buộc: small icon cho status bar
            .setCustomContentView(remoteViews)              // Layout cho trạng thái thu gọn
            .setCustomBigContentView(remoteViews)           // Layout cho trạng thái mở rộng (big)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true);

        NotificationManagerCompat.from(this).notify(NOTIFICATION_ID_CUSTOM, builder.build());
        Toast.makeText(this, "Đã gửi Custom Notification", Toast.LENGTH_SHORT).show();
    }
}

Bước 7: NotificationViewActivity (src/NotificationViewActivity.java)

package com.app.sample;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class NotificationViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.notification_view);

        String title = getIntent().getStringExtra("title");
        String text = getIntent().getStringExtra("text");

        TextView tvTitle = findViewById(R.id.title);
        TextView tvText = findViewById(R.id.text);

        tvTitle.setText(title);
        tvText.setText(text);
    }
}

Bước 8: Manifest (androidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.app.sample">

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Android 13+ -->

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                
                
            </intent-filter>
        </activity>

        <activity android:name=".NotificationViewActivity" />

    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. Nhấn 2 nút để so sánh Standard vs Custom Notification. Kéo xuống notification shade để xem custom layout.

RemoteViews - Các phương thức quan trọng

Phương thức Chức năng
setTextViewText(viewId, text) Set text cho TextView
setTextColor(viewId, color) Set màu chữ (API 23+)
setTextViewTextSize(viewId, unit, size) Set cỡ chữ
setImageViewResource(viewId, resId) Set image resource cho ImageView
setImageViewBitmap(viewId, bitmap) Set bitmap cho ImageView
setViewVisibility(viewId, visibility) VISIBLE/INVISIBLE/GONE
setOnClickPendingIntent(viewId, pendingIntent) Xử lý click trên view trong notification
setProgress(viewId, max, progress, indeterminate) Cập nhật ProgressBar

Lưu ý quan trọng về RemoteViews

  • Chỉ hỗ trợ subset View/Layout: LinearLayout, RelativeLayout, FrameLayout, GridLayout, TextView, ImageView, Button, ProgressBar, Chronometer, AnalogClock, ViewStub.
  • KHÔNG hỗ trợ: ConstraintLayout (API 24+ mới hỗ trợ một phần), RecyclerView, ListView, WebView, EditText, custom View.
  • Layout phải đơn giản: Tránh nested quá sâu, animation, selector.
  • setCustomContentView: Layout thu gọn (collapsed) - thường 1 dòng.
  • setCustomBigContentView: Layout mở rộng (expanded) - khi user kéo xuống.
  • setCustomHeadsUpContentView: Layout cho Heads-up notification (API 24+).

Android 13+ (API 33): Runtime Permission POST_NOTIFICATIONS

// Manifest
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

// Runtime request
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) 
        != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 
            REQUEST_CODE_NOTIFICATION);
    }
}

Thư viện hiện đại: NotificationCompat + Material Design

  • Dùng NotificationCompat.Builder từ androidx.core:core:1.12.0.
  • Style có sẵn: NotificationCompat.DecoratedCustomViewStyle - tự động thêm icon, header, actions cho custom view.
  • MediaStyle, MessagingStyle, BigPictureStyle, InboxStyle cho các trường hợp chuyên biệt.

Kết luận

Custom Notification với RemoteViews cho phép branding mạnh, hiển thị thông tin phức tạp, nhưng bị hạn chế về View hỗ trợ. Luôn test trên nhiều phiên bản Android (đặc biệt API 24+, 26+, 33+).