カテゴリー
JavaScript

Chrome ExtensionのService_WorkerとContent_Script

Service_Worker(Background)とContent_Script(DOM側)の関係が少し分かりにくかったので、書きとめておきます。

以下ではService_Workerをbackground.js、Content_Scriptをcontent.jsとファイル名で呼びます。

content.jsにはDOM(Document Object Model)とのやりとりに関するコード、background.jsにはその他の処理を記述する、と捉えると分かりやすいです。

content.jsとbackground.js間のやりとりはmessagesを介して行います。

background.jsからcontent.jsへは現在アクティブなtab情報を利用してmessagesを送るので、chrome.tabs.sendMessages APIを使います。

// background.js

function setListenerOnMenus() {
  chrome.contextMenus.onClicked.addListener( async (info, tab) => {
    chrome.tabs.sendMessage(tab.id, {
      // some message. can be blank
      something: "some message",
      somethingElse: "another message"
    }, (response) => {
      // receiver of the message (content.js) can return a respond
      // in a callback
      // background.js can respond to that response here...
      let context = response.context;
      let url = response.url;
      // do something with it!...
    }
  }
}
// content.js
chrome.runtime.onMessage.addListener( (request, sender, sendResponse) => {
  // request contains messages sent from background.js. Can retrieve as below...
  let something = request.something;
  let somethingElse = request.somethingElse;
  
  // also, content.js can interact/manipulate DOM element and communicate it back to background.js
  let url = window.location.href;
  let context = window.getSelection().anchorNode.data;
  // can communicate back in the callback below...
  sendResponse({
    context: context,
    url: url
  });
}

background.jsにはonStartup listenerを追加することで、extension使用時に起動するようになります。

// background.js

chrome.runtime.onStartup.addListener(() => {
  setup();
});
カテゴリー
JavaScript

Chrome Extensions v3からGoogle Drive APIを操作する

Manifest v2とv3ではだいぶ実装方法が異なるようで、Web上にはv3の情報が少なかったため、最小実装の方法を調べて実装してみました。

(結局、Query Stringをどう書くのかを調べるのが一番大変だった、、というAPIあるある、、でしたがw)

AuthTokenをゲットする方法は前回のpostを参照ください。

fileをGoogle Drive内の特定のParentフォルダ内に作成するには以下のコードを使います。変数parentはファイルidです。

ちなみにGoogle Driveではfolderとfile、どちらもfileとして扱い、mimeTypeによって区別されます。

公式DocumentによるとSheets APIは現時点では特定のフォルダにspreadsheetを作成することができないため、作成後にファイルを移動するか、Drive APIを使用します。

また、このようなネットワーク通信などのAsynchronous(非同期)な処理はPromiseを使って、コールする時にasync/awaitできるようにしておくと勝手が良いです。

// using Drive API
function createSpreadsheet(token, parent, fileName) {
  return new Promise((resolve, reject)=>{
    const fileMetadata = {
      "name" : fileName,
      "mimeType" : "application/vnd.google-apps.spreadsheet",
      // by specifying the parents, the API will create the file in that folder
      "parents" : [parent]
    }
    fetch("https://www.googleapis.com/drive/v3/files", {
      method: "POST",
      headers: {
        "Content-Type" : "application/json", 
        "Authorization" : `Bearer ${token}`
      },
      body: JSON.stringify(fileMetadata)
    }).then((document)=>{
      return document.json();
    }).then(async (result)=>{
      menus[result.name] = result.id;
      await initialSetupOfSheet(token, result.id);
      resolve();
    }).catch((error)=>{
      console.log("in createSpreadsheet: ", error);
      reject(error);
    });
  });
}

以下は、ファイル名でサーチするコード。

ちなみに、Google Drive APIではsearch query部分を以下のようにq=mimeType=とし、また、’application/vnd.google-apps.spreadsheet’とクォートで囲まなければ認識しないようで、URLSearchParamsをすんなりと使わせてくれなかったためこのようにしています。

function getFileByName(token, fileType, fileName) {
    fetch(`https://www.googleapis.com/drive/v3/files?fields=files(name,id,kind,mimeType,trashed)&q=mimeType='application/vnd.google-apps.${fileType}'+and+name+contains+'${fileName}'`, {
      method: "GET",
      headers: { 
        "Authorization" : "Bearer " + token,
      },
      // can't have body in this method
    }).then((response) => {
      return response.json();
    });
}

Parentフォルダ内のファイルをゲットするコード

function getTheFilesOrCreateIfNone(token, fileId){
  return new Promise((resolve, reject)=>{
    fetch(`https://www.googleapis.com/drive/v3/files?q="${fileId}"+in+parents&mimeType='application/vnd.google-apps.spreadsheet'&fields=files(name,kind,id,mimeType,trashed)`, {
      method: "GET",
      headers: {
        "Authorization": "Bearer " + token
      },
    }).then((result)=>{
      console.log("checking if working: ", result);
      return result.json();
    }).then(async (result)=>{
    // do what ever...
      console.log(result.files);
      console.log("files including in trash: ", result.files.length);
      let files = result.files.filter((file)=>{
        return file.trashed == false;
      })
      // if there is (are) spreadsheet(s), set the context menu
      if (files.length > 0) {
        for (const file of files) {
          menus[file.name] = file.id;
        }
      } else {
        // create new spreadsheet called "Words" in the app folder
        await createSpreadsheet(token, _folderId, "Words");
      }
      // success
      setMenus();
      resolve();
    }).catch((error)=>{
      // fail
      reject(error);
    });
  });
}

Spreadsheetにデータを挿入するためのコード

function writeToSheet(token, fileId, text, context, url) {
  console.log("writing data to the sheet");
  return new Promise((resolve, reject) => {
    const range = "A1:E1";
    const date = Date();
    const bodyData = {
      "range" : range,
      "majorDimension" : "ROWS",
      "values" : [
        [text, context, "", url, date]
      ]
    };
    fetch(`https://sheets.googleapis.com/v4/spreadsheets/${fileId}/values/${range}:append?insertDataOption=INSERT_ROWS&valueInputOption=RAW`, {
      method: "POST",
      headers: {
        // Content-Type is needed, otherwise "parse error"
        "Content-Type" : "application/json", 
        "Authorization" : `Bearer ${token}`
      },
      body: JSON.stringify(bodyData),
    }).then((response) => {
      return response.json();
    }).then((response) => {
      console.log(response);
      resolve();
    }).catch((error) => {
      console.log(error);
      reject("in writeToSheet", error);
    });
  });
}
カテゴリー
JavaScript

Chrome ExtensionsでOAuth2 Authorization

OAuth2 AuthorizationにはExtensionのKeyが必要です。Keyを入手するには、

まず、ベーシックなChrome Extensionを作成し、chrome://extensions/ページのPack Extensionでパックします。

crxファイルとpemファイルが作成されるので、chrome://extensions/ページ内にcrxファイルをドラッグするとExtensionがインストールされます。

macOSの場合は、/Users/{username}/Library/Application Support/Google/Chrome/Default/ExtensionsにIDと同じ名前のフォルダーが作成されています。その中のmanifest.jsonにあるkeyをコピーし、開発中のExtensionのmanifest.jsonにコピーペーストします。

“permissions”に”identity”と”identity,email”を追加します。

// manifest.json
{
    "manifest_version" : 3,
    "name" : "Save The Word",
    "version" : "1.0",
    "description" : "A word saving app",

    "background" : {
        "service_worker" : "scripts/background.js"
    },

    "permissions" : [
        "contextMenus",
        "identity",
        "identity.email"
    ],

    "icons" : {
        "16" : "images/book_16.png",
        "32" : "images/book_32.png",
        "48" : "images/book_48.png",
        "128" : "images/book_128.png"
    },

    "key": "YOUR_VERY_LONG_KEY_HERE",

    "content_scripts" : [
        {
            "js" : ["scripts/content.js"],
            "run_at" : "document_idle",
            "matches" : [
                "<all_urls>"
            ]
        }
    ]
}

次に、インストールされたExtensionを削除し、Load Unpackedから開発中のExtensionを開くことで、Extensionの開発を続けることができます。

background.js内に以下を記述し、service_workerのコンソールにidentity Objectがプリントすることで、上手く働いているかを確認出来ます。

console.log(chrome.identity);

次にhttps://console.cloud.google.com/でプロジェクトを作成し、

APIs & ServicesのOAuth consent screenでExternalを選択し、Createします。

次に、CredentialsのCREATE CREDENTIALSからOAuth Client IDを選択し、

Chrome appを選択、Client名とExtension IDを入力します。

次に、LibraryからGoogle People APIをEnableします。

CREATE CREDENTIALSでAPI Keyを選択します。

manifest.jsonに以下を追記します。

// manifest.json

"oauth2" : {
    "client_id" : "YOUR_CLIENT_ID",
    "scopes" : [
        "profile email",
        "https://www.googleapis.com/auth/contacts"
    ]
},

background.jsに以下を追記すると、tokenがプリントされます。

chrome.identity.getAuthToken({ interactive: true }, (token) => {
  console.log("got token: ", token);
});

このtokenを使ってspreadsheets apiでsheetを作成するには、

chrome.identity.getAuthToken({ interactive: true }, (token) => {
  createSheet(token);
});

function createSheet(token) {
  const data = {
    // requires "properties" to be the most outer key
    "properties" : {
      "title" : "Test Sheet"
    }
  }
  fetch("https://sheets.googleapis.com/v4/spreadsheets", {
    method: "POST",
    headers: {
      "Content-Type" : "application/json",
      "Authorization" : `Bearer ${token}`
    },
    body: JSON.stringify(data)
  }).then((document) => {
    console.log(document.json());
  });
}

Google Drive APIの実装はこのPostをご参照ください。

カテゴリー
Linux

DigitalOceanでMySQLインストール時にエラー

メモリーを512MByteから1GByteにアップグレードすると良いみたい。。

記事

そして、mysql_secure_installation の時のエラー対策の記事はこちら

カテゴリー
JavaScript

Chrome Extensionを作ってみた

Chrome Extensionを作る機会があったので、書きとめておきます。

まずはプロジェクトフォルダを作成し、manifest.jsonというJSONファイルを作成します。このファイルはフォルダのrootに置く必要があります。なお、このJSONファイル内のコメントはChrome Web Storeにアップロードする前に削除する必要があります。

このJSONファイルの必須Keyは”manifest_version”、”name”、”version”の三つです。

その他の色々なアクション、JacvaScriptファイルやアイコン画像の在りか(path)についてもここに記述します。

“background” 内の”service_worker”はTabに表示されるWeb pageとは関係なく実行されるJavaScriptを記述し、”content_scripts”はTabに表示されるWeb pageを操作するためのJavaScriptを記述します。”run_at”はこのscriptが実行されるタイミングを指定します。”document_start”, “document_end”, “document_idle”の三つから選択します。

manifestの仕様

// manifest.json
{
    "manifest_version" : 3,
    "name" : "Save The Word",
    "version" : "1.0",
    "description" : "A word saving app",

    // background works separate from the web page loaded in the tab
    "background" : {
        "service_worker" : "scripts/background.js"
    },

  // content_scripts works to interact and manipulate the web page loaded in the tab
    "content_scripts" : [
        {
            "js" : ["scripts/content.js"],
            "run_at" : "document_idle",
            "matches" : [
                "<all_urls>"
            ]
        }
    ]
}
// scripts/content.js

console.log("hello");

chrome://extensions/にアクセスし、Developer modeをオン、Load unpackedからプロジェクトフォルダを読み込むと、https://から始まるあらゆるサイトを読み込む時に、ブラウザのコンソールに”hello”がプリントされると、content.jsが上手く読み込まれていることが確認出来ます。

Web page内のテキストを右クリックすると、chromeのcontext menuにextensionが表示され、そのテキストを使って何かをするためのコードは以下。

var menus = {
    "Save" : "Save",
    "Copy" : "Copy"
};

chrome.runtime.onInstalled.addListener( () => {
    for(let key of Object.keys(menus)) {
        chrome.contextMenus.create({
            id: key,
            title: key,
            type: "normal",
            contexts: ["selection"]
        });
    }
});

chrome.contextMenus.onClicked.addListener( (item, tab) => {
    const text = item.selectionText;
    console.log(text);
});

selectionTextの周囲の文章もゲットしたい場合は、DOMを操作できるcontent_scriptへメッセージを送りたいため、以下のようにします。ちなみに、content_scriptからbackgroundへ送る場合はchrome.runtime APIを使いますが、backgroundからcontent_scriptへ送る場合はchrome.tab APIを使ってtabを指定する必要があります。公式document

// in background.js
chrome.contextMenus.onClicked.addListener(function(item, tab){
    const text = item.selectionText;
    chrome.tabs.sendMessage(tab.id, { greeting: text });
});

// in content.js
chrome.runtime.onMessage.addListener( (request, sender, sendResponse) => {
    console.log("selected word: ", request.greeting);
    console.log("selected context: ", window.getSelection().anchorNode.data);
});

service_workerのコンソールの表示はこちらをクリック

カテゴリー
Android Java

Android 101 (初歩)

Trivial thing as adding a square can be intimidating when you start out coding…

So, here are some of the steps to guide you through how to grasp the very basic concepts in Android development.

When you create new project in Android Studio, it automatically generates a below code for you.

public class MainActivity extends AppCompatActivity {

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

The code “1” basically says, “to set the activity_main.xml as the root layout”. In Android, you must have a root layout to contain other child elements such as layouts, views, buttons, etc.

There are other types of layouts such as RelativeLayout, etc. You can learn more in Google’s official document.

In a real-world scenario, you will often be required to write your program in “code”, so let’s see how we can replace this line “programmatically”

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 2. root LinearLayout
        LinearLayout rootLinearLayout = new LinearLayout(this);
        rootLinearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
        rootLinearLayout.setOrientation(LinearLayout.VERTICAL);
        setContentView(rootLinearLayout);
    }
}

in code “2”, we are creating a LinearLayout object and setting it as the contentView. You can think of it as the “root container for everything else” in that particular screen.

LinearLayout needs a layoutParams, and it is set to “MATCH_PARENT”, so it stretches to fit the entirety of the device screen.

LinearLayout automatically lines up any child element vertically or horizontally. Here, we are setting it to “VERTICAL”

When you build the project, you get this.

It looks pretty,,, BLANK!, but congratulations, you have just created a rootLinearLayout from scratch, programmatically.

Next, let’s add color to this rootLayout. It is easy as this.

rootLinearLayout.setBackgroundColor(Color.CYAN);

In case you want to change the App title, you can change it in app -> manifests -> AndroidManifests.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name" // 3
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SimpleSquareExample"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

</manifest>

The code “3” is the line you want to look at. It says “@string/app_name”. It means a variable “app_name” is kept in a string file for the app name. You can find the string file at app -> res -> values -> strings.xml

<resources>
    <string name="app_name">Simple Square Example</string>
</resources>

In case you don’t want the app name to be displayed at all, you can set it in the onCreate() method (4).

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide(); // 4
        // root LinearLayout
        LinearLayout rootLinearLayout = new LinearLayout(this);
        rootLinearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
        rootLinearLayout.setOrientation(LinearLayout.VERTICAL);
        rootLinearLayout.setBackgroundColor(Color.CYAN);
        setContentView(rootLinearLayout);
    }
}

Let’s now add a child element to the rootLinearLayout.

Follow 5, 6 and 7 in the code below.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        // root LinearLayout
        LinearLayout rootLinearLayout = new LinearLayout(this);
        rootLinearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
        rootLinearLayout.setOrientation(LinearLayout.VERTICAL);
        rootLinearLayout.setBackgroundColor(Color.CYAN);
        setContentView(rootLinearLayout);

        // 5. creating a layout params
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);

        // 6. creating an element, color it red and set the weight
        RelativeLayout elementOne = new RelativeLayout(this);
        elementOne.setBackgroundColor(Color.RED);
        params.weight = 1;
        elementOne.setLayoutParams(params);

        // 7. add child element to the rootLinearLayout
        rootLinearLayout.addView(elementOne);

    }
}

I’m sure you are wondering, what is “weight”…?

It will be clear once you add another element to the rootLinearElement. Add the below code “8” in your onCreate() method.

// 8. creating another element, and adding to the rootLinearLayout
RelativeLayout elementTwo = new RelativeLayout(this);
elementTwo.setBackgroundColor(Color.BLUE);
elementTwo.setLayoutParams(params);

rootLinearLayout.addView(elementTwo);

Do you get the point? LinearLayout lets you align child elements in vertical or horizontal fashion. But, what if you want to change the ratio of the element size, like make the blue square bigger than the red?

It is easy. You create another layout params and set the weight as you like. Your entire code will look something like this. The code 9 and 10 are where you want to look at.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        // root LinearLayout
        LinearLayout rootLinearLayout = new LinearLayout(this);
        rootLinearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
        rootLinearLayout.setOrientation(LinearLayout.VERTICAL);
        rootLinearLayout.setBackgroundColor(Color.CYAN);
        setContentView(rootLinearLayout);

        // creating a layout params
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);

        // creating an element
        RelativeLayout elementOne = new RelativeLayout(this);
        elementOne.setBackgroundColor(Color.RED);
        params.weight = 1;
        elementOne.setLayoutParams(params);

        // add child element to the rootLinearLayout
        rootLinearLayout.addView(elementOne);

        // 9. create another layout params
        LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
        params2.weight = 2;

        // creating another element, and adding to the rootLinearLayout
        RelativeLayout elementTwo = new RelativeLayout(this);
        elementTwo.setBackgroundColor(Color.BLUE);
        // 10. set params2 to the element
        elementTwo.setLayoutParams(params2);

        rootLinearLayout.addView(elementTwo);

    }
}

“weight” can be any floating point value.

Next, let’s make the corner of the square rounded. Use a class called GradientDrawable.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        // root LinearLayout
        LinearLayout rootLinearLayout = new LinearLayout(this);
        rootLinearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
        rootLinearLayout.setOrientation(LinearLayout.VERTICAL);
        rootLinearLayout.setBackgroundColor(Color.CYAN);
        setContentView(rootLinearLayout);

        // creating a layout params
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);

        // creating an element
        RelativeLayout elementOne = new RelativeLayout(this);
        elementOne.setBackgroundColor(Color.RED);
        params.weight = 1;
        elementOne.setLayoutParams(params);

        // add child element to the rootLinearLayout
        rootLinearLayout.addView(elementOne);

        // create another layout params
        LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
        params2.weight = 2;

        // 11. for rounded corner
        GradientDrawable gd = new GradientDrawable();
        gd.setColor(Color.YELLOW);
        gd.setCornerRadius(200);

        // creating another element, and adding to the rootLinearLayout
        RelativeLayout elementTwo = new RelativeLayout(this);
        elementTwo.setBackgroundColor(Color.BLUE);
        // 12. set gd as the background. Noticed the element's background color is no longer blue, but yellow?
        elementTwo.setBackground(gd);
        // set params2 to the element
        elementTwo.setLayoutParams(params2);

        rootLinearLayout.addView(elementTwo);
    }
}

You can create complex layout just by using LinearLayout or RelativeLayout, and add child elements to it, however ways you want. Follow steps 13 – 19 to see how it works.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        // root LinearLayout
        LinearLayout rootLinearLayout = new LinearLayout(this);
        rootLinearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
        rootLinearLayout.setOrientation(LinearLayout.VERTICAL);
        rootLinearLayout.setBackgroundColor(Color.CYAN);
        setContentView(rootLinearLayout);

        // creating a layout params
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);

        // creating an element
        // 13. change the layout type to LinearLayout
        LinearLayout elementOne = new LinearLayout(this);
        // 14. set orientation to HORIZONTAL
        elementOne.setOrientation(LinearLayout.HORIZONTAL);
        elementOne.setBackgroundColor(Color.RED);
        params.weight = 1;
        elementOne.setLayoutParams(params);

        // 15. creating childElements
        LinearLayout childElementOne = new LinearLayout(this);
        childElementOne.setBackgroundColor(Color.GREEN);
        // 16.  create a layout params
        LinearLayout.LayoutParams childLP = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT);
        childLP.weight = 1;
        childElementOne.setLayoutParams(childLP);

        // 17. adding childElement to elementOne
        elementOne.addView(childElementOne);

        // 18. creating another child element
        LinearLayout childElementTwo = new LinearLayout(this);
        childElementTwo.setBackgroundColor(Color.BLUE);
        childElementTwo.setLayoutParams(childLP);

        // 19. adding it to the parent element
        elementOne.addView(childElementTwo);

        // add child element to the rootLinearLayout
        rootLinearLayout.addView(elementOne);

        // create another layout params
        LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
        params2.weight = 2;

        // for rounded corner
        GradientDrawable gd = new GradientDrawable();
        gd.setColor(Color.YELLOW);
        gd.setCornerRadius(200);

        // creating another element, and adding to the rootLinearLayout
        RelativeLayout elementTwo = new RelativeLayout(this);
        elementTwo.setBackgroundColor(Color.BLUE);
        elementTwo.setBackground(gd);
        // set params2 to the element
        elementTwo.setLayoutParams(params2);

        rootLinearLayout.addView(elementTwo);

    }
}

Lastly, you can get crazier elements inside RelativeLayout. Follow steps 20 – 30.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        // root LinearLayout
        LinearLayout rootLinearLayout = new LinearLayout(this);
        rootLinearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
        rootLinearLayout.setOrientation(LinearLayout.VERTICAL);
        rootLinearLayout.setBackgroundColor(Color.CYAN);
        setContentView(rootLinearLayout);

        // creating a layout params
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);

        // creating an element
        // change the layout type to LinearLayout
        LinearLayout elementOne = new LinearLayout(this);
        // set orientation to HORIZONTAL
        elementOne.setOrientation(LinearLayout.HORIZONTAL);
        elementOne.setBackgroundColor(Color.RED);
        params.weight = 1;
        elementOne.setLayoutParams(params);

        // creating childElements
        LinearLayout childElementOne = new LinearLayout(this);
        childElementOne.setBackgroundColor(Color.GREEN);
        // create a layout params
        LinearLayout.LayoutParams childLP = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT);
        childLP.weight = 1;
        childElementOne.setLayoutParams(childLP);

        elementOne.addView(childElementOne);

        LinearLayout childElementTwo = new LinearLayout(this);
        childElementTwo.setBackgroundColor(Color.BLUE);
        childElementTwo.setLayoutParams(childLP);

        elementOne.addView(childElementTwo);

        // add child element to the rootLinearLayout
        rootLinearLayout.addView(elementOne);

        // create another layout params
        LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
        params2.weight = 2;

        // for rounded corner
        GradientDrawable gd = new GradientDrawable();
        gd.setColor(Color.YELLOW);
        gd.setCornerRadius(200);

        // creating another element, and adding to the rootLinearLayout
        RelativeLayout elementTwo = new RelativeLayout(this);
        elementTwo.setBackgroundColor(Color.BLUE);
        elementTwo.setBackground(gd);
        // set params2 to the element
        elementTwo.setLayoutParams(params2);

        rootLinearLayout.addView(elementTwo);

        // 20. creating child elements for elementTwo
        RelativeLayout childElementA = new RelativeLayout(this);
        childElementA.setBackgroundColor(Color.RED);
        // 21. new layout params and set width and height to 200 
        RelativeLayout.LayoutParams rlp1 = new RelativeLayout.LayoutParams(200, 200);
        // 22. add rule to make it align to the parent's top
        rlp1.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        childElementA.setLayoutParams(rlp1);
        // 23. generating an ID for the view. View.generateViewId() will automatically generate an ID that does not conflict with other IDs. It is a lot better than you manually managing them.
        childElementA.setId(View.generateViewId());

        // 24. adding it to the elementTwo
        elementTwo.addView(childElementA);

        // 25. creating another child element
        RelativeLayout childElementB = new RelativeLayout(this);
        childElementB.setBackgroundColor(Color.MAGENTA);
        // 26. creating another layout params. This time, set the width and height to 400
        RelativeLayout.LayoutParams rlp2 = new RelativeLayout.LayoutParams(400, 400);
        // 27. adding a rule to make it just below childElementA
        rlp2.addRule(RelativeLayout.BELOW, childElementA.getId());
        // 28. you can even add margins to it!
        rlp2.topMargin = 50;
        // 29. add another rule to make it align to the right of childElementA
        rlp2.addRule(RelativeLayout.RIGHT_OF, childElementA.getId());
        // 30. again, you can add whatever margins to it!
        rlp2.leftMargin = 100;
       
        childElementB.setLayoutParams(rlp2);

        elementTwo.addView(childElementB);
    }
}

Oh, yes. One final tip. In order to get the perfect circle, set the cornerRadius the half size of the shape. Follow, 31 – 33

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        // root LinearLayout
        LinearLayout rootLinearLayout = new LinearLayout(this);
        rootLinearLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
        rootLinearLayout.setOrientation(LinearLayout.VERTICAL);
        rootLinearLayout.setBackgroundColor(Color.CYAN);
        setContentView(rootLinearLayout);

        // creating a layout params
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);

        // creating an element
        // change the layout type to LinearLayout
        LinearLayout elementOne = new LinearLayout(this);
        // set orientation to HORIZONTAL
        elementOne.setOrientation(LinearLayout.HORIZONTAL);
        elementOne.setBackgroundColor(Color.RED);
        params.weight = 1;
        elementOne.setLayoutParams(params);

        // creating childElements
        LinearLayout childElementOne = new LinearLayout(this);
        childElementOne.setBackgroundColor(Color.GREEN);
        // create a layout params
        LinearLayout.LayoutParams childLP = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT);
        childLP.weight = 1;
        childElementOne.setLayoutParams(childLP);

        elementOne.addView(childElementOne);

        LinearLayout childElementTwo = new LinearLayout(this);
        childElementTwo.setBackgroundColor(Color.BLUE);
        childElementTwo.setLayoutParams(childLP);

        elementOne.addView(childElementTwo);

        // add child element to the rootLinearLayout
        rootLinearLayout.addView(elementOne);

        // create another layout params
        LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
        params2.weight = 2;

        // for rounded corner
        GradientDrawable gd = new GradientDrawable();
        gd.setColor(Color.YELLOW);
        gd.setCornerRadius(200);

        // creating another element, and adding to the rootLinearLayout
        RelativeLayout elementTwo = new RelativeLayout(this);
        elementTwo.setBackgroundColor(Color.BLUE);
        elementTwo.setBackground(gd);
        // set params2 to the element
        elementTwo.setLayoutParams(params2);

        rootLinearLayout.addView(elementTwo);

        // creating child elements for elementTwo
        RelativeLayout childElementA = new RelativeLayout(this);
        childElementA.setBackgroundColor(Color.RED);
        RelativeLayout.LayoutParams rlp1 = new RelativeLayout.LayoutParams(200, 200);
        rlp1.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        childElementA.setLayoutParams(rlp1);
        childElementA.setId(View.generateViewId());

        elementTwo.addView(childElementA);

        RelativeLayout childElementB = new RelativeLayout(this);
        childElementB.setBackgroundColor(Color.MAGENTA);

        RelativeLayout.LayoutParams rlp2 = new RelativeLayout.LayoutParams(400, 400);

        // 31. create another GradientDrawable
        GradientDrawable gd2 = new GradientDrawable();
        // 32. btw, you can assign any color by using Color.argb method.
        gd2.setColor(Color.argb(255, 252, 186, 3));
        // 33. set cornerRatius to 200 (half of 400)
        gd2.setCornerRadius(200);
        childElementB.setBackground(gd2);

        rlp2.addRule(RelativeLayout.BELOW, childElementA.getId());
        rlp2.addRule(RelativeLayout.RIGHT_OF, childElementA.getId());
        rlp2.leftMargin = 100;
        rlp2.topMargin = 50;
        childElementB.setLayoutParams(rlp2);

        elementTwo.addView(childElementB);
    }
}
カテゴリー
iOS Swift

UIViewをsubviewごとRotateしたい

sample code

square.transform = CGAffineTransformMakeRotation(CGFloat.pi/2) のようにすると可能

import UIKit

class ViewController: UIViewController {
  
  var viewWidth: CGFloat {
    return view.bounds.width
  }
  
  var viewHeight: CGFloat {
    return view.bounds.height
  }

  lazy var square: UIView = {
    let view = UIView()
    view.backgroundColor = .red
    return view
  }()
  
  lazy var smallSquare: UIView = {
    let view = UIView()
    view.backgroundColor = .blue
    return view
  }()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    view.backgroundColor = .white
    
    view.addSubview(square)
    square.addSubview(smallSquare)
  }
  
  override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    layout()
  }
  
  private func layout() {
    let squareWidth: CGFloat = 300
    let squareHeight: CGFloat = 200
    
    let innerSquareWidth: CGFloat = 250
    let innerSquareHeight: CGFloat = 150
    
    square.frame = CGRect(x: viewWidth/2 - squareWidth/2, y: viewHeight/2 - squareHeight/2, width: squareWidth, height: squareHeight)
    smallSquare.frame = CGRect(x: square.bounds.width/2 - innerSquareWidth/2, y: square.bounds.height/2 - innerSquareHeight/2, width: innerSquareWidth, height: innerSquareHeight)
    
    square.transform = CGAffineTransformMakeRotation(CGFloat.pi/2)
  }
}